在自学数据结构与算法(java描述)的时候,会对泛型作相应的要求。自己对java是半吊子,希望在这样模块化的学习中,能对java更熟悉
WHAT is 泛型
定义:泛型就是参数化类型。
类比在使用方法时,需要将实参传入签名中的形参一样,同样可以将具体类型(只能是类类型,包括自定义类,不能是基本类型如int等)作为类型实参传入一个类、方法。此时,类和方法的声明中要求声明类型形参。
通过这种方式来限定使用的类、接口、方法中的类型
也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
例如一个泛型类的定义与使用:
//定义
class ClassName { //尖括号中内容为类型形参,即:private T varName; //泛型标识(成员变量类型)变量名.....
}
}
//实例化
ClassName s = new ClassName;//将String作为类型实参传入
特性:泛型只有在编译阶段才有效。例:
List stringArrayList = new ArrayList();
List integerArrayList = new ArrayList();
Class classStringArrayList=stringArrayList.getClass();
Class classIntegerArrayList=integerArrayList.getClass();if(classStringArrayList.equals(classIntegerArrayList)){
Log.d("泛型测试","类型相同");
}
上述代码输出结果为“D/泛型测试: 类型相同”。
即泛型的使用,在编译器编译过程中,检查泛型结果正确后,会将泛型的相关信息擦除(去泛型化)。同时在对象进入或离开方法的边界处添加类型检查和类型转换的方法。
实际上,泛型在逻辑上(在编译结束前)可以看成是多种类型,实际上(运行时)是相同的基本类型。
WHY we use 泛型
我觉得,了解一种手段的目的是非常有助于学习的。就目前,我的认识,泛型是为了保证容器内参数类型相同,减少错误操作或将运行时错误,转换成编译时错误。
例如:
List arrayList = newArrayList();
arrayList.add("aaaa");
arrayList.add(100);for(int i = 0; i< arrayList.size();i++){
String item=(String)arrayList.get(i);
Log.d("泛型测试","item = " +item);
}
这里,未使用泛型,在循环返回容器内元素时,会产生“java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String”的错误,原因在于忽略了容器内存在无法转型为String的类型(integer)
所以,使用泛型的代码
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);//注解:编译时报错
for(int i = 0; i< arrayList.size();i++){
String item=(String)arrayList.get(i);
Log.d("泛型测试","item = " +item);
}
会在注解处报错,从而在编译阶段就能找到潜在的问题。
为什么要使用泛型又可以深入的理解,挖坑待填
HOW to use 泛型
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法
泛型类:
泛型类的基本写法:
//定义
class ClassName { //尖括号中内容为类型形参,即:
private T varName; //泛型标识(成员变量类型)变量名
.....
}
}//实例化
ClassName s = new ClassName;//将String作为类型实参传入
例如一个最普通的泛型类:
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型//在实例化泛型类时,必须指定T的具体类型
public class Generic{//key这个成员变量的类型为T,T的类型由外部指定
privateT key;public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key =key;
}public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
returnkey;
}
}/*** 实例化*/
//泛型的类型参数只能是类类型(包括自定义类),不能是简单类型//传入的实参类型需与泛型的类型参数类型相同,即为Integer.
Generic genericInteger = new Generic(123456);//传入的实参类型需与泛型的类型参数类型相同,即为String.
Generic genericString = new Generic("key_vlaue");
Log.d("泛型测试","key is " +genericInteger.getKey());
Log.d("泛型测试","key is " + genericString.getKey());
输出为:
D/泛型测试: key is 123456
D/泛型测试: key is key_vlaue
定义了泛型类,在使用的时候,如果不传入类型实参会怎么样。那泛型中的成员都会是Object类型。add容易,get难,例如:在WHY we use 泛型中的第一段代码,在循环打印成员的时候,需要进行类型转换,将Object向下转型为String或Integer。
注:泛型的类型参数只能是类类型,包括自定义类,不能是基础类型(int,float,double etc.)
//在这个类中,只能有这一种类型,会在编译阶段检查,编译完成后擦除
泛型接口:
泛型接口与泛型类相似,常被用在各种生产器件中
//定义一个泛型接口
public interface Generator{publicT next();
}
泛型接口在实现的时候有两种情况:
#1传入类型实参,
#2不传入类型实参,
/*** 传入泛型实参时:
* 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator
* 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
* 即:Generator,public T next();中的的T都要替换成传入的String类型。*/
public class FruitGenerator implements Generator{private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
@OverridepublicString next() {
Random rand= newRandom();return fruits[rand.nextInt(3)];
}
}
/**#2* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class FruitGenerator implements Generator{
* 如果不声明泛型,如:class FruitGenerator implements Generator,编译器会报错:"Unknown class"*/
class FruitGenerator implements Generator{
@OverridepublicT next() {return null;
}
}
#1 好理解,就是在实现类实现接口的时候就确定了泛型类型,
#2 具体来说就是,实现类实现接口的时候不确定内部类型,需要在实例化对象的时候才能确定,所以相当于将实现类创建成泛型类 参考https://www.cnblogs.com/shadowdoor/p/6817809.html
泛型通配符
泛型通配符就是“?”。他要这么用:
public void showKeyValue1(Generic>obj){
Log.d("泛型测试","key value is " +obj.getKey());
}
这个方法的签名中就使用了泛型通配符,即可以表示所有的类型。,那么为什么要有这么一个通配符,他解决了什么问题呢
比如定义一个方法
public void showKeyValue1(Genericobj){
Log.d("泛型测试","key value is " +obj.getKey());
}
那么,这个方法可以处理Generic类型的实参吗,换句话说Generic可以向上转型为Generic吗
答案是不可以,虽然Number和Integer是继承关系,但是其泛型类不是
//定义方法
public void showKeyValue1(Genericobj){
Log.d("泛型测试","key value is" +obj.getKey());
}
Generic gInteger = new Generic(123);
Generic gNumber = new Generic(456);
showKeyValue(gInteger );//showKeyValue这个方法编译器会为我们报错:Generic//cannot be applied to Generic//showKeyValue(gInteger);
因此引入通配符“?”,从而Generic>可以表示所有Generic<>泛型的父类。
修改成通配符后,代码可以正常编译运行。
//修改成通配符
public void showKeyValue1(Generic>obj){
Log.d("泛型测试","key value is" +obj.getKey());
}
Generic gInteger = new Generic(123);
Generic gNumber = new Generic(456);
showKeyValue(gInteger );
注意:,此处’?’是类型实参,而不是类型形参。
在搜索资料的时候,又发现了一些新问题,目前,尖括号中有三种内容\\>,他们有什么区别呢
与>都是实参,实在使用泛型的时候用的,而是一个形参,是在声明泛型的时候用的;而是所有泛型的父类,与其他泛型没有父子关系,例Generic> generic = new Generic,Generic generic = new Generic
泛型方法
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。
/*** 泛型方法的基本介绍
*@paramtClass 传入的泛型实参
*@returnT 返回值为T类型
* 说明:
* 1)public 与 返回值中间非常重要,可以理解为声明此方法为泛型方法。
* 2)只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
* 3)表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
* 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。*/
public T genericMethod(Class tClass)throwsInstantiationException ,
IllegalAccessException{
T instance=tClass.newInstance();returninstance;
}
泛型方法的基本使用:
public classGenericTest {//这个类是个泛型类,在上面已经介绍过
public class Generic{privateT key;publicGeneric(T key) {this.key =key;
}//我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。//这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。//所以在这个方法中才可以继续使用 T 这个泛型。
publicT getKey(){returnkey;
}/*** 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
* 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
public E setKey(E key){
this.key = keu
}*/}/*** 这才是一个真正的泛型方法。
* 首先在public与返回值之间的必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
* 这个T可以出现在这个泛型方法的任意位置.
* 泛型的数量也可以为任意多个
* 如:public K showKeyName(Generic container){
* ...
* }*/
public T showKeyName(Genericcontainer){
System.out.println("container key :" +container.getKey());//当然这个例子举的不太合适,只是为了说明泛型方法的特性。
T test =container.getKey();returntest;
}//这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic这个泛型类做形参而已。
public void showKeyValue1(Genericobj){
Log.d("泛型测试","key value is " +obj.getKey());
}//这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?//同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
public void showKeyValue2(Generic>obj){
Log.d("泛型测试","key value is " +obj.getKey());
}/*** 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
* 虽然我们声明了,也表明了这是一个可以处理泛型的类型的泛型方法。
* 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
public T showKeyName(Generic container){
...
}*/
/*** 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
* 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
* 所以这也不是一个正确的泛型方法声明。
public void showkey(T genericObj){
}*/
public static voidmain(String[] args) {
}
}
泛型方法可以出现杂任何地方和任何场景中使用。但是有一种情况是非常特殊的,当泛型方法出现在泛型类中时,我们再通过一个例子看一下
public classGenericFruit {classFruit{
@OverridepublicString toString() {return "fruit";
}
}class Apple extendsFruit{
@OverridepublicString toString() {return "apple";
}
}classPerson{
@OverridepublicString toString() {return "Person";
}
}class GenerateTest{public voidshow_1(T t){
System.out.println(t.toString());
}//在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。//由于泛型方法在声明的时候会声明泛型,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
public voidshow_3(E t){
System.out.println(t.toString());
}//在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
public voidshow_2(T t){
System.out.println(t.toString());
}
}public static voidmain(String[] args) {
Apple apple= newApple();
Person person= newPerson();
GenerateTest generateTest = new GenerateTest();//apple是Fruit的子类,所以这里可以
generateTest.show_1(apple);//编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person//generateTest.show_1(person);//使用这两个方法都可以成功
generateTest.show_2(apple);
generateTest.show_2(person);//使用这两个方法也都可以成功
generateTest.show_3(apple);
generateTest.show_3(person);
}
}
泛型方法与可变参数
public voidprintMsg( T... args){for(T t : args){
Log.d("泛型测试","t is " +t);
}
}
printMsg("111",222,"aaaa","2323.4",55.55);
静态方法与泛型
静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法
public class StaticGenerator{
....
..../*** 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
* 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
* 如:public static void show(T t){..},此时编译器会提示错误信息:
"StaticGenerator cannot be refrenced from static context"*/
public static voidshow(T t){
}
}
泛型方法总结
泛型方法能使方法独立于类而产生变化,以下是一个基本的指导原则
无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,
那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。
所以如果static方法要使用泛型能力,就必须使其成为泛型方法。
泛型方法内容比较多,更多更细致的总结还是等自己时间充足再做吧。继续搬砖