Java泛型
大约 3 分钟
1. 基本使用
Java为了代码复用,设计了泛型以让多种数据类型执行相同的代码,使用场景如下:
// 1. 类
class Container<T>{}
class Container<K,V>{}
// 2. 接口
interface Info<T>{
public T getVar();
}
// 3. 方法
public <T> T getObject(Class<T> c) {}
xxx.getObject(Class.forName("com.xx.xx"));
泛型方法的意义在于:泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新new一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活
2. 上下限
下面代码中,B是A的子类,所以funcA(b)
满足了里氏替换原则,不会报错。但下面用泛型的则会报错
class A{}
class B extends A {}
// 如下两个方法不会报错
public static void funA(A a) {
// ...
}
public static void funB(B b) {
funA(b);
// ...
}
// 如下funD方法会报错
public static void funC(List<A> listA) {
// ...
}
public static void funD(List<B> listB) {
funC(listB);
// ...
}
此时使用extends
即可解决,表示泛型可以是A或者A的子类
public static void funC(List<? extends A> listA) {
// ...
}
public static void funD(List<B> listB) {
funC(listB); // OK
// ...
}
同理,<? super A>
代表A的自身或者其父类
3. 类型擦除
Java中的泛型是伪泛型,在编译阶段会进行类型擦出(Type Erasure),其原则是:
- 消除类型参数声明,即删除<>及其包围的部分
- 根据类型参数的上下界替换类型为原生态类型:如果无上下界则替换为Object,如果存在上下界则根据子类替换原则取类型参数的最左边限定类型(即父类)
- 为了保证类型安全,必要时插入强制类型转换代码
- 自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”
注意泛型不能在静态变量或方法上使用,因为泛型是实例化时指定的
例如:
// 擦除前
public Info<T extends Number> {
private T value;
}
// 擦除后
public Info {
private Number value;
}
4. 桥接方法
对于下面的泛型类及其子类,因为存在类型擦除,所以Pair
的T
实际上变成了Object
,而此时DateInter
因为Date
和Object
不匹配,理论上并不能算作是重写(因为参数类型变了,所以是重载)。
class Pair<T> {
private T value;
public T getValue() {
return value;
}
}
class DateInter extends Pair<Date> {
@Override
public Date getValue() {
return super.getValue();
}
}
但实际上不是这样的,因为编译器为我们在子类自动生成了桥方法,我们调用的Date getValue
会自动通过Object getValue
去调用父类
class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {
com.tao.test.DateInter();
Code:
0: aload_0
1: invokespecial #8 // Method com/tao/test/Pair."<init>":()V
4: return
public java.util.Date getValue(); //我们重写的getValue方法
Code:
0: aload_0
1: invokespecial #23 // Method com/tao/test/Pair.getValue:()Ljava/lang/Object;
4: checkcast #26 // class java/util/Date
7: areturn
public java.lang.Object getValue(); //编译时由编译器生成的桥方法
Code:
0: aload_0
1: invokevirtual #28 // Method getValue:()Ljava/util/Date 去调用我们重写的getValue方法;
4: areturn
}