Java 泛型

泛型类


public class Pair<T>{

private T first;

private T second;



public Pair(){first = null; second = null;}

public Pair(T first, T second){this.first = first; this.second = second;}



public T getFirst(return firsts;)

public T getSecond(return second;)



public void setFirst(T newValue){first = newValue;}

public void setSecond(T newValue){second= newValue;}

}

泛型类可以有多个类型变量:public class Pair<T, U>{...}

泛型类可以看做普通类的工厂

泛型方法


class ArrayAlg{

// <T> 放在返回类型前

public static <T> T getMiddle(T... a){ // 参数为不定长数组

return a[a.length/2];

}

}



// 注意泛型方法的调用方式(在方法前加<TYPE>)

String middle = ArrayAlg.<String>getMiddle("John", "Q.","Public");



// 实际上,上面的表达式中也可以省略<String>,因为编译器可推测出来,但如果多种类型的参数就不行了

类型变量的限定

有时候,类或方法要对类型变量加以约束,比如某类型必须具有 compareTo 方法:

public static <T extends Comparable> T min(T[] a){}

限制为 T 必须为实现了 Comparable 的类

一个类型变量或通配符可以有多个限定,中间用&分隔,例如


<T extends Comparable & Serializable>

泛型代码和虚拟机

虚拟机没有泛型类型对象,所有对象都属于类

无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type),原始类型的名字就是山区类型参数后的泛型类型名,擦除erased类型变量,并替换为限定类型(无限定的变量用 Object)

结果就是一个普通的类而已

1. 翻译泛型表达式


Pair<Employee> buddies = ...;

Employee buddy = buddies.getFirst();

调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换(Object转换为Employee)

2. 翻译泛型方法

下面这个日期区间类是继承自Pair<Date>泛型, 并覆盖了 setSecond 放方法(参数显示指定为Date)

由于父类的 setSecond 被类型擦除了,其参数是 Object 而不是Date:

public void setSecond(Object second){...},因此这个方法没有被覆盖,而是被继承了下来

但实际上我们调用 DateInterval 对象的 setSecond 是想调用其自己的方法,这里类型擦除和多态就发生了冲突。

需要编译器在 DateInterval 类中生成桥方法(bridge method)

上面 pair 被声明为类型 Pair,这个类型只有一个方法setSecond(Object),然后再用 pair 引用的对象(DateInterval类型)调用 DateInterval.setSecond(Object)方法(这是一个合成的桥方法,它再去调用 DateInterval.setSecond(Date)来解决冲突

在擦除类型中,有两个 setSecond 方法:


Date getSecond();

Object getSecond();

不允许在Java代码中这样编写(参数类型必须不一样),但虚拟机是用参数类型和返回类型共同来确定一个方法的(上述是可以的),所以编译器可能产生两个仅返回类型不同方法字节码,虚拟机能够正确处理这一情况。

总之要清楚有关 Java 泛型转行的事实:

  • 虚拟机中没有泛型,只有普通的类和方法

  • 所有的类型参数都用它们的限定类型替换

  • 桥方法被合成来保持多态

  • 为保持类型安全性,必要时插入强制类型转换

泛型的约束与局限

不能用基本类型实例化类型参数,只有 Pair<Double> 没有 Pair<double>

(类型擦除后编程 Object 域,不能存储double值)

虚拟机中对象总有一个非特定的非泛型类型,因此所有类型查询只产生原始类型:

只能是 if(a instanceof Pair),不能是if(a instanceof Pair)

也不能进行强制类型转换(只能检测出 a 是 Pair 类型,没有Pair这种类型)

不能创建实例参数化类型的数组,例如Pair<String>[] table = new Pair<String>[10];,因为类型擦除后table的类型是Pair[],但是存入一个不是 String 的 Pair 也是可以的,比如Pair<Double>,这就不对了,Java不允许创建泛型数组,虽然仅仅声明是合法的。

不能实例化类型变量,比如new T()

Class 类本事是泛型,比如String.class是一个Class<String>的实例

不能构造泛型数组

泛型类的静态上下文中类型变量无效

因为类型擦除后,如果使用了引用变量,其静态域能就有多个,但static是共享的,静态域只能有一个

不能抛出或捕获泛型类的实例

泛型类型的继承规则

无论 S 和 T是什么关系(继承or whatever),Pair<S>Pair<T>没有什么联

通配符类型

Pair<? extends Employee>

之前Pair<T extends xxx>是设计泛型类或方法时用的,以上通配符是声明泛型时用的

还可以使用无限定通配符

Pair<?>Pair本质的不同在于,可以用任意Object对象调用原始的Pair类的setObject


Pair<?>



// 有以下方法

? getFirst(); // 返回值只能赋给一个 Object

void setFirst(?); // 不能被调用,Object也不行,但null可以