文章目录
泛型的概念
jdk1.5 出现的安全机制
,之前不指定类型时,任何类型都能丢入容器,取出集合中的元素时,取出的元素都是object类型,无法直接获知存入时元素的类型,所以在获取时,要转换成某一种类型时会不安全
,因而产生了泛型机制。具体而言,泛型为:
编译
时,对存入
的集合元素类型进行检查,保证集合内元素类型统一
,将运行时期可能出现的ClassCastException
(存入类型不能强制转化成取出类型时抛出
)转到了编译时期(存入时)。运行
时,对取出
的集合元素自动强制类型转换
为存入时的类型,即不再是Object类型。- 基本类型不对应一个类,因而
不能用基本类型实例化类型参数
,即<>中不能是基本类型,只能是类
泛型的意义
定义时
面向的类型任意
,使用
时再确定
具体类型。
泛型的擦除和补偿
-
泛型的擦除
生成的class文件
中不带泛型
。擦除原因:在
编译时期
,已经对集合中的元素进行了类型检查
,达到了目的,所以不在需要在运行时仍然利用泛型,同时也为了让泛型产生之前“运行的类加载器”仍可以在泛型出现之后使用,从而不用再重写之前的类加载器。 -
泛型的补偿
在运行时期
,JVM在取出集合中元素时,自动
通过调用对象的getClass()获取元素的类型并完成类型转换动作。不用使用者显示地强制转换了.Pair<Integer> p1 = new Pair<Integer>(1,100); System.out.println(Pair.class==p1.getClass()); //输出true
泛型类和泛型接口
泛型类和泛型接口的区别可以看做是类和接口的区别。因此,此处主要讲泛型类。
- 定义: class 类名/接口名 <T,E,……>{}
- 特点:
-
泛型类和泛型接口中泛型的确定是在
创建实例时
,通过构造函数中的<>
才确定
的 -
使用泛型类时,如果没有指定泛型对应的具体类时,默认对应的类型为Object
-
由于泛型擦除,泛型类并不会生成单独的.class文件
List<Number> list1 = new ArrayList<>(); List<Integer> list2 = new ArrayList<>(); System.out.println(list1.getClass() == list2.getClass());//输出true
-
instanceof是
运行时
判断,泛型已经被擦除,不能判断具体泛型,为了避免让程序员误解,不允许后面泛型类的<>里面是具体类型if(ex_num instanceof Generic<Number>){ } //报错,后面是Generic<Number>编译报错,前面的ex_num可以是泛型类生成的对象 if(ex_num instanceof Generic){ } //正确 if(ex_num instanceof Generic<?>){ } //正确
-
泛型类中的泛型对应的对象,由于不能确定具体类型为何,只能调用Object方法
class A<T>{ T t1; public void method(){ t1.toString(); //t1只能调用Object中的方法 } }
- 在泛型中,
泛型类型不同
,就认为是互不相关的不同的类
//参数类型是子类和父类关系,应用在泛型中时,这种关系不再存在 public void test(List<Object> c){} //定义一个参数为List<Object>的函数 List<String> strList = new ArrayList<>(); //创建一个List<String>类型的变量 test(strList); //编译报错,List<Object>和List<String>是无关的类,没有子父类关系
-
泛型方法
-
定义格式:修饰符 <T, S,…,Z> 返回值类型 方法名 (形参列表)
注意:只有在定义时,签名中有<>才是泛型方法,仅仅是使用了类中定义的泛型参数的方法并不是泛型方法。
-
特点:
泛型声明中的
类型形参,独立于类
而产生变化。函数声明中的类型形参和类中的类型形参被视作不同的泛型类型(即使两者名字一样),为避免误解,两者不要重复
- 所在的类可以是泛型类,也可以不是泛型类。
- 可以使用泛型类中定义的类型形参,使用的
泛型类的类型形参
仍然依赖于泛型类
函数声明
中的类型形参
是在调用该函数时
,通过实参确定
的,因此形参列表需要使用到函数声明中的泛型类型。形参列表没有用到函数声明中的类型形参的话,不报错,但是会导致类型形参因为不能确定具体类型而无法在函数内部使用。因为泛型函数的泛型类型是在调用该函数时才确定,因此泛型函数在被同一个对象调用多次时,每次对应的泛型类型可以不同。
- 静态方法无法使用泛型类中的泛型参数,想要使用泛型,只能被声明为泛型方法。
静态方法和对象无关,泛型类中的泛型参数是通过创建对象时调用构造函数实现的,因此,静态方法不能使用泛型类中的泛型参数。
class A<E>{ E e; /*关于构造函数*/ public A(){} //正确 public A(E e){} //正确,使用了类中类型参数的构造函数,非泛型方法 public <T> A(T e){} //正确,构造函数也是泛型方法 /*关于静态函数*/ public static <T> void method1(T t){}//正确,静态函数要使用泛型,必须定义为泛型方法 public static void method1(E e){} //错误,静态方法不能使用类中定义的泛型 /*关于泛型函数*/ public <E> void method2(E e){ this.e = e; //会报错,函数中的泛型E和类中的泛型E被视为不同泛型参数,两个E没有关系,相互独立 } public <T> void method3(T t1,E e1){ //可以使用泛型类中的泛型E,泛型E的类型依赖于泛型类 E e2 = e1; T t2 = t1; } public <T> void method4(){ T t; //不报错, System.out.println(t); //报错,函数无参数传入,无法确定T的具体类型,因而t无法和其他变量相互赋值 } public <T> void method5(T t) { //正确,因为T的类型可以在调用时确定 System.out.println(t); } public static void main(String[] args){ A<String> a = new A<String>(); /*同一个对象,两次调用泛型函数,对应的泛型类型可以不同*/ a.method5(2); //此时,E对应的是String类,T对应的是Integer类 a.method5("a"); //此时,E对应的是String类,T对应的也是String类 } }
使用类中泛型的方法
- 定义: 声明中没有泛型,使用的泛型参数
完全依赖于类
的方法(即不能自己定义新的参数类型的方法)class A<E>{ E e; public void method1(E e) { //正确,泛型E的类型和具体的对象有关 System.out.println(e); } public void method2(T t){ //错误,不是泛型函数,不能使用类中未定义的泛型 } public static void main(String[] args){ A<String> a = new A<String>(); //对象是String类型 a.method1("a"); //只能传入String类型 } }
构造函数使用泛型
-
定义时: 格式为类名(),而非类名<类型形参>()
-
使用时: 格式为类名() 或者 类名<>() 或者 类名<类型实参>()
注意:只有在调用构造函数时的菱形语法
中<>可以为空
,其他时候<>内都不能为空菱形语法:创建泛型类对象时,后面构造函数中<>可以为空,其类型由前面的引用类型确定
public class A<T> { public A(){} //正确 public A<T>{} //格式错误 public <E> A(E e){} //构造函数也可以是泛型函数 public static void main(String[] args){ //调用构造函数public A(){} A<String> a1 = new A<String>(); //正确 A<String> a2 = new A<>(); //正确,和上面等同。使用了菱形语法,即Java编译器可以推断出<>中缺失的信息 A a2 = new A<String>(); //错误,因为前面 "A a3" 相当于 "A<Object> a3",而A<Object>和A<String>互不兼容 A a4 = new A(); //正确,但不提倡,因为此时类A的泛型对应的是Object,失去了设置泛型的作用 A a5 = new A<>(); //正确,相当于 A<Object> a5 = new A<>(); //调用构造函数public <E> A(E e){} A<Integer> a5 = new A<Integer>(123); //正确 A<Integer> a6 = new A(123); //正确,和上面等同 A a8 = new A(123); //正确,和上面等同 } }
泛型的通配符:? T
- ?:
- 可以接受任意类型的
类型实参
,因而可以表示各种泛型的父类 可被取,不可存
- 只能在<>中使用,不能脱离<>而表示任意类型。即不能是单独的?
不能单独
在定义
泛型中使用,<?>只能
在使用
泛型时使用。除非是泛型限定时可以在定义中用。
class A<?>{} //编译报错 /*这时候通配符会捕获具体的String类型,但编译器不叫它String,而是起个临时的代号, 比如”CAP#1“。所以以后再也不能往list里存任何元素,包括String。唯一能存的就是null*/ List<?> c = new ArrayList<String>(); //表示c可以接受任何List泛型。 c.add(new String()); //发生编译错误 c.add(null); //正确,null是所有引用类型的实例
- 可以接受任意类型的
- T :
- 只能接受泛型参数是<T>的
类型形参
- 可以脱离<>,被当做某种类型而操作
- 既可以在定义泛型时使用,也可以在使用泛型时使用
- 只能接受泛型参数是<T>的
泛型的限定
和类的继承一样,指定类型形参上限时,至多一个父类上限,可以多个类型接口上限,并且接口上限必须位于类上限之后。
泛型的限定,让本来由于泛型不同而没有关系的类,产生了关系
- 设定类型通配符的限定
由于存在 ?,程序无法确定这个受限制的通配符的具体类型,所以带有限定的泛型同样不能被改变,只能被使用
。- <? extends 类型实参A & 接口实参1 & 接口实参2……>: 指定接收上限
只能存不能取,因为不知道取出的元素是什么类型,也就无法取。比如 添加元素 addAll.因为取出都是按照上限类型来运算的,不会出现安全隐患。 - <? super 类型实参A & 接口实参1 & 接口实参2……>: 指定接收下限。
可以存,因为最小粒度确定了,可以取,但取出的元素由于不能确定类型只能当做Object。比如比较器。
因为取出对象的时候要用父类进行接收。
- <? extends 类型实参A & 接口实参1 & 接口实参2……>: 指定接收上限
- 设定类型形参的限定
- <T extends 实际类型A & 接口实参1 & 接口实参2……>:
- <T super 实际类型A & 接口实参1 & 接口实参2……>:
泛型类的继承
非泛型类 继承 泛型类
使用泛型类时(除非是<?>作为一个可以接收任何泛型类型的引用的场景),必须确定其泛型类型,因此:
public class A extends B //正确,其中B为带泛型的类,相当于public class A extends B<Object>
public class A extends B<String> //正确,继承时指定父类的泛型参数,子类就不用再写泛型参数,如果写了,那就是子类自己新增加的
public class A extends B<T> //错误。父类类型参数如果写出就必须要被确定类型,即要么是实参,要么是可以通过子类类型确定的形参;否则不写
泛型类 继承 泛型类
- 子类和父类泛型参数一样时,子类和父类泛型参数
一定
是形参(子类泛型一定是形参,父类泛型中类型和子类一样时,表示相同泛型,因而父类也是形参) - 子类和父类泛型参数不一样时,子类泛型参数是形参,父类泛型参数为
必须
实参(因为根据子类泛型参数推不出父类泛型参数)public class A<T> extends B<T> //正确 public class A<Integer> extends B<Integer> //注意,定义带有泛型的类或接口时,子类泛型参数总是形参,不是实参。这里父类和子类的参数类型Integer 不是java.lang.Integer。它只是一个泛型参数名称 ,恰好相同,跟T是没有区别的 public class A<T> extends B<String>//正确,父类指定了类型,子类又增加了,这时子类的只是新增加的泛型参数,跟父类没有关系 public class A<T,S> extends B<T> //正确 public class A<T,E extends T> extends B<T> //正确
非泛型类的函数 覆盖 泛型类的函数
由于此时父类泛型参数已经明确,因而子类覆盖父类方法的时候,必须需要用具体的类型
class Father<T> {
T info;
public Father(T info) {
this.info = info;
}
public T get() {
return info;
}
public void set(T info) {
this.info = info;
}
}
class Son extends Father<String> {
// 所有从父类继承来的方法的类型参数全部都确定为String了,因此在覆盖的时候都要使用具体的类型实参了!
public Son(String info) {
super(info);
}
@Override
public String get() {
return "haha";
}
@Override
public String set(String info) {
return "lala";
}
}
常见问题
泛型和数组的区别
如果Fu是Zi的父类,则 Fu[] 依然是 Zi[] 的父类,但是G<Fu>和G<Zi>之间没有子父类关系(或者说互不兼容)(举例)
泛型类 vs 泛型函数
泛型类 | 泛型函数 | |
---|---|---|
泛型作用域 | 整个类,不能被static成员使用 | 函数内部,static函数只能是泛型函数 |
泛型类型确定 | 创建引用或者对象时确定 | 调用函数时确定 |
空 <?> <Object>区别
class A<T>{
A a1; //相当于A a1<Object>; 只不过此时不会像A a2<Object>一样进行安全检查
A a2<?> ; //表示可以接受任意类型,如A<String>,但是不能取
A a2<Object>; //只接受A<Object>类型的对象,不接受其他类型,如A<String>
参考文献
https://blog.csdn.net/s10461/article/details/53941091 —泛型详解和举例
https://blog.csdn.net/ShierJun/article/details/51253870 —泛型类的继承
https://blog.csdn.net/tounaobun/article/details/8587348 —静态方法的泛型
https://www.jianshu.com/p/fa66f06b701b —使用泛型时的注意事项
https://blog.csdn.net/Lirx_Tech/article/details/51570138 泛型讲解,易理解
https://www.zhihu.com/question/31429113 ? 和 T 的区别
https://www.zhihu.com/question/20400700/answer/117464182 泛型的限定
http://www.voidcn.com/article/p-fvtpxxtk-brb.html 通配符和Object