定义泛型类
示例如下:
1 |
|
在Java中,使用变量
E表示集合的元素类型,K和V分别表示表的关键字与值的类型。T(需要时好i可以用临近的字母U和S)表示”任意类型”
泛型方法
定义泛型方法
1 | public class Util { |
使用泛型方法
1 | String information = Util.<String>getMiddle("12345","qww","sss"); |
注意:例如double d = Util.getMiddle(1.0,1234,0),编译器会先找到Double和Integer的超类,但是
它们的超类和接口,都是泛型类型。这种情况下,会报错,唯一的解决办法就是把所有参数写成double
类型变量的限定
语法:
1 | <T extends 类型> |
示例:
1 | public class Util<T extends Comparable>{ |
注意:在Java继承中,可以根据需要拥有多个接口类型,但限定中至多有一个类。如果用
类限定,它必须是限定列表中的第一个。
类型擦除
Java虚拟机中没有泛型对象,所有的对象都属于普通类。无论何时定义一个泛型类型,在Java虚拟机
中都会擦除类型变量,并替换为限定类型(无限定的变量用Object)
例如,Pair<T>的原始类型为:
1 | public class Pair<Object> {//泛型类可以有多个类型变量,例如 Pair<T,U> |
由于T是一个无限定的变量,所以直接用Object替代。
在程序中可以包含不同类型的Pair,例如,Pair<String>或Pair<LocalDate>.而
擦除类型后就变成原始的Pair类型。
如果有限定,例如:
1 | public class Util<T extends Serializable&Comparable>{ |
原始类型会用第一个限定类型来替代。上例中就为Serializable,编译器只在必要的时候
插入Comparable强制类型转换。为了提高效率应该将标记接口(即没有方法的接口,比如Serializable)放在后面
有返回值的泛型方法
当调用有返回值的泛型方法时,编译器会插入强制类型转换,例如:
1 | Pair<String> pair = ....; |
转换为:
1 | Pair<String> pair = ....; |
桥方法
1 | public class Test extends Pair<LocalDate> { |
由上面的示例,当你继承Pair<LocalDate>时,由于类型擦除,Pair<LocalDate>中只存在setSecond(Object sencod)方法,当我们
想通过Pair<LocalDate> pair = new Test()来调用setSencod时,就
与多态发生冲突。
为了解决这个问题,编译器会在Test中生成桥方法,例如
1 | public void setSecond(Object second){ |
如果我们覆盖getSecond()方法,则存在两个getSecond()方法
1 | LocalDate getSecond() |
注意:具有相同的参数类型的两个方法是不合法的,不能写这样的Java代码。
但是,在虚拟机中,用参数类型和返回值类型来确定一个方法。因此,编译器可
产生两个仅返回值类型不同的方法
Java泛型转换的总结
- 虚拟机中没有泛型,只有普通的类和方法
- 所有的类型参数都用它们的限定类型替代
- 桥方法被合成保持多态
- 为保持类型的安全性,必要时插入强制类型转换
泛型的约束
不能用基本类型来实例化类型参数
例如Pair<double>是不被允许的,不过可以使用基本类型的包装类
运行时类型查询只适用于原始类型
例如:
1 | a instanceof Pair<String> //编译器报错 |
不能创建参数化类型数组
例如
1 | Pair<String>[] p = new Pair<String>[10];//编译器报错 |
不过可以声明通配符(后面介绍)类型的数组,然后进行类型转换
1 | Pair<String>[] p = (Pair<String>[])new Pair<?>[10];//结果不安全,由于数组储存只会检查擦除后的类型 |
注意:对于可变参数(本质是数组),虽然违法了上面的规则,但是你只会收到一个警告
不能直接实例化类型变量
例如:
1 | T t = new T();//编译器报错 |
实例化类型变量的方法
1.方法1,使用反射
1 | public class C<T> { |
2.方法2,使用构造器表达式
1 | public class C<T> { |
不能抛出或捕获泛型类的实例
可以消除对受查异常的检查
注意擦除后的冲突
泛型类型的继承规则
注意:无论T和S是什么关系(比如继承),通常,C<T>和C<S>没有什么联系
永远可以将一个参数化类型转换为一个原始类型,例如,C<String>是原始类型C的子类型
通配符
1 | List<A> list = new ArrayList(); |
为了解决这个问题,就可以使用通配符,例如
1 | List<? extends A) list = new ArrayList(); |