-
- 2.1. 类型变量的限定
-
- 6.1. 统配符的限定
- 6.2. 通配符限定的行为差异
- 6.3. 无限定通配符
- 6.4. 通配符捕获
泛型程序设计
在java增加泛型类之前,泛型程序设计使用继承实现的(Object),当获取一个值时还需进行强制类型转换,很不方便。泛型提供了一个更好的解决方案:类型参数。
1 | var files = new ArrayList<String>(); |
他会让程序更易读,更安全。
1. 定义简单的泛型类
泛型类就是有一个或多个类型变量的类。
下面定义了一个泛型类
1 | public class Pair<T> { |
Pair类引进了一个类型变量T,用尖括号括起来,放在类名的后面。泛型类可以有多个类型变量:
1 | public class Pair<T,U>{....} |
类型变量在整个类定义中用于指定方法的返回类型以及字段和局部变量的类型,例如
1 | private T first; |
可以使用具体的类型替换类型变量来实例化泛型类型
1 | Pair<String> |
2. 泛型方法
不仅可以定义泛型类,还可以定义泛型方法
1 | class ArrayAlg{ |
这是在普通类中定义的泛型方法,也可以在泛型类中定义。
注意:类型变量放在类型修饰符的后面,返回类型的前面
当调用一个泛型方法时,可以把具体类型包围在尖括号中,放在方法名前面
1 | String middle = ArrayAlg.<String>getMiddle("j","x"); |
大多时候可以省略具体类型。
2.1. 类型变量的限定
用关键字extends对类型变量进行限定
1 | <T extends BoundingType> |
表示T是限定类型BoundingType的子类型。他们可以是类,也可以是接口
一个类型变量或通配符可以有多个限定,用”&”分隔,而逗号用于分隔类型变量
1 | <T extends Comparable & Serializable,U> |
3. 泛型代码和虚拟机
虚拟机没有泛型类型对象———所有对象都属于普通类。虚拟机会擦除类型参数
3.1. 类型擦除
无论何时定义一个泛型类型,都会自动提供一个相应的原始类型,这个原始类型的名字就是去掉类型参数后的泛型类型名。
在虚拟机中,类型变量会被擦除,并替换为其限定类型,无限定类型的变量则替换为Object
上面的Pair类的原始类型如下形式
1 | public class Pair{ |
T是一个无限定的变量,直接用Object替换掉
对于有限定的类型变量,会被替换为第一个限定,例如:
1 |
|
3.2. 转换泛型表达式
编写一个泛型方法调用时,如果擦除了返回类型,编译器会加入强制类型转换
1 | Pair<Employee> buddies = ....; |
虚拟机会把这个方法调用转换为两条虚拟机指令
- 对原始方法Pair.getFirst 的调用。
- 对返回类的Object类型强制转换为Employee类型。
当访问一个泛型字段时也要加入强制类型转换,和调用泛型方法时一样。
3.3. 转换泛型方法
当一个类继承自一个泛型类,并重写泛型类中的方法
1 | public class brige extends Pair<String> { |
这时可能会产生一些问题,当类型擦除过后,Pair类中的show方法的参数会被替换成为Object
1 | public void show(Object value){...}//父类方法 |
则此时父类中方法的参数和子类重写方法中的参数不一致!类型擦除和多态产生了冲突,在使用多态的时候会产生问题。
为了解决这个问题,编译器会在子类中生成一个桥方法来保持多态,我们使用反射来看一下子类(brige)中生成的桥方法
1 | public class brigeDemo { |
可以看见,在brige类中增加了一个参数为Object的方法,这个方法即为桥方法,必要时也会加入强制类型转换。
总之,对于java泛型的转换,需要记住以下几个事实:
- 虚拟机中没有泛型,只有普通的类和方法。
- 所有的类型参数都会替换成为它们的限定类型或Object。
- 会合成桥方法来保持多态。
- 为保持类型安全性,必要时会插入强制类型转换。
4. 限制与局限性
下面讨论下使用泛型的限制
- 不能用基本类型替代类型参数,可以使用他们的包装器类型。
- 运行时的类型查询(instanceof)只适用于原始类型,使用强制类型转换转换为一个泛型类时,会得到一个警告。
- getClass方法总是返回原始类型,来验证一下
1 | Pair<String> pair = new Pair<>(); |
- 不能创建参数化类型的数组,但可以声明带泛型的数组引用。
1 | ArrayList<String>[] arr = new ArrayList[5];//OK |
- 不能实例化类型变量,不过可以通过反射来构造泛型对象。
1 | public static <T>T makePair(Class<T> cl) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { |
不能构造泛型数组。T[] t = new T[5]//error
泛型类的静态上下文中类型变量无效,静止使用带有类型变量的静态字段和方法。(在泛型类中)
1
2
3
4
5public static T makePair(Class<T> cl) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
return cl.getConstructor().newInstance();
}//error
private static T singleInstance;//error不能抛出或捕获泛型类的实例。泛型类甚至不嫩扩展Throwable类。
可以利用泛型取消对检查型异常的检查。(参数类型为RuntimeException)
注意擦除后的冲突。如果两个接口类是同一接口的不同参数化,一个类或类型变量就不能同时作为这两个接口类型的子类。
1 | class Employee implements Comparable<Employee>{...} |
5. 泛型类的继承规则
现在有一个类Manager继承自Employee,那么Pair< Manager>和Pair< Employee>有什么关系呢?
答案是没有关系。
不过泛型类可以扩展或实现其他的泛型类。
从泛型类派生子类需要注意以下几点:
- 子类也是泛型类,子类和父类的泛型类型要一致。
1 | public class brige<T> extends Pair<T>{...} |
- 子类不是泛型类,父类要明确泛型的数据类型。
1 | public class brige extends Pair<String>{...} |
6. 通配符类型
在通配符类型中,允许类型参数发生变化。例如,通配符类型
1 | Pair<? extends Employee> p |
表示任何泛型Pair类型,它的类型参数是Employee的子类,如
Pair< Manager>,但不是Pair< String>。
类型Pair< Manager>是Pair<? extends Employee>的子类型。
6.1. 统配符的限定
通配符的限定有超类型限定(super),也有子类型限定(extends)。
- 超类型限定super
1 | Pair<? super Manager> p |
这个通配符限制为Manager的所有超类型。包括Object。
- 子类型限定extends,与超类型限定相反。
1 | Pair<? extends Employee> p |
6.2. 通配符限定的行为差异
超类型限定和子类型限定在使用方法时有一些差异:
- 超类型限定可以为方法提供参数,但不能使用返回值。
- 子类型限定的行为与超类型限定相反。
下面来看一组代码来加深理解
1 | public static void show(Pair<? extends Employee> p){//子类型限定 |
总之:
- 带有超类型限定的通配符允许你写入一个泛型对象,能写入类型为super关键字之后的类型,在这为Manager,它一定是通配符类型的子类。
- 而带有子类型限定的通配符允许你读取一个泛型对象,读取出的对象只能赋给extends关键字之后的类型或其父类,在这为Employee,它一定是通配符类型的父类。
- 理解以上的关键点是父类引用能指向子类对象,而子类引用不能指向父类对象。
6.3. 无限定通配符
还可以使用根本无限定的通配符,例如Pair<?>。
该类型既不能读取也不能写入,可以用在不需要实际类型的方法之中,如:
1 | public static boolean hasNulls(Pair<?> p){ |
6.4. 通配符捕获
有以下两个方法
1 | public static void swap(Pair<?> p){swapHelper(p);} |
第一个方法调用第二个方法,第二个方法的参数T会捕获通配符。
7. 反射和泛型
Class类也是泛型类,可以利用这个来免除类型转换。
1 | Class<Pair> pairClass = Pair.class; |
其中构造实例的返回类型直接是Pair,不需要再进行强制类型转换。