及其子类型的一种。所以,我们对popAll方法的设计就存在逻辑上的问题。因此,应改为:
// Wildcard type for parameter that serves as an E consumer
public void popAll(Collection<? super E> dst) {
while (!isEmpty())
dst.add(pop());
}
这样,我们就可以实现将 Stack 中的元素读取到我们的 Collection 中。在上述例子中,在调用 pushAll方法时,src生产了E实例(produces E instances);在调用popAll方法时 dst 消费了E实例(consumes E instances)。Naftalin与Wadler将PECS称为Get and Put Principle。
此外,我们再来学习一个例子:java.util.Collections的copy方法(JDK1.7),它的目的是将所有元素从一个列表(src)复制到另一个列表(dest)中。显然,在这里,src是生产者,它负责产生T类型的实例;dest是消费者,它负责消费T类型的实例。这完美地诠释了PECS:
// List<? extends T> 类型的 src 囊括了所有 T类型及其子类型 的列表
// List<? super T> 类型的 dest 囊括了所有可以将 src中的元素添加进去的 List种类
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
// 将 src 复制到 dest 中
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
因此,输入参数是生产者时,用 ? extends T;输入参数是消费者时,用 ? super T;输入参数既是生产者又是消费者时,那么通配符类型没什么用了:因为你需要的是严格类型匹配,这是不用任何通配符而得到的。无界通配符<?> 既不能做生产者(读出来的是Object),又不能做消费者(写不进去)。
四.编译器如何处理泛型?
通常情况下,一个编译器处理泛型有两种方式:
1、Code Specialization
在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型list,可能需要针对string,integer,float产生三份目标代码。
2、Code Sharing
对每个泛型类只生成唯一的一份目标代码;该泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换。
C++中的模板(template)是典型的Code specialization实现
C++编译器会为每一个泛型类实例生成一份执行代码。执行代码中integer list和string list是两种不同的类型。这样会导致代码膨胀(code bloat),不过有经验的C++程序员可以有技巧的避免代码膨胀。另外,在引用类型系统中,这种方式会造成空间的浪费。因为引用类型集合中元素本质上都是一个指针,没必要为每个类型都产生一份执行代码,而这也是Java编译器中采用Code sharing方式处理泛型的主要原因。
Java编译器通过Code Sharing方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除(type erasue)实现的。
五. 类型擦除
1、要点
类型擦除,通过移除泛型类定义的类型参数并将定义中每个类型变量替换成对应类型参数的非泛型上界(第一个边界),得到原生类型(raw type)。类型擦除是 Java 泛型实现的一种折中,以便在不破坏现有类库的情况下,将泛型融入Java并且保证兼容性(泛型出现前后的Java类库互相兼容)。类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码(Class 对象)上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且在必要的时候添加类型检查和类型转换的方法。
擦除是在编译期完成的。类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。泛型类型只有在静态类型检查期间才会出现,在此之后,程序中的所有泛型类型都将被擦除,并替换为它们的非泛型上界。因此,在泛型代码内部,无法获得任何有关泛型参数类型的信息。
2、编译器是如何配合类型擦除的?

3、类型擦除的主要过程
对于Pair<>:
// 代码示例 A
class Pair {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
Pair<>的原始类型为:
// 代码示例 B
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
以下类型擦除示例:
// 代码示例 1
interface Comparable <A> {
public int compareTo( A that);
}
//.............................类型擦除后........................................
// 代码示例 1
interface Comparable {
public int compareTo( Object that);
}
// 代码示例 2
final class NumericValue implements Comparable <NumericValue> {
priva byte value;
public NumericValue (byte value) { this.value = value; }
public byte getValue() { return value; }
public int compareTo( NumericValue that) { return this.value - that.value; }
}
//.............................类型擦除后........................................
// 代码示例 2
final class NumericValue implements java.lang.Comparable{
// 域
private byte value;
// 构造器
public NumericValue(byte);
// 方法
public int compareTo(NumericValue);
public volatile int compareTo(java.lang.Object); //桥方法
public byte getValue( );
}
// 代码示例 3
class Collections {
public static <A extends Comparable<A>> A max(Collection <A> xs) {
Iterator<A> xi = xs.iterator();
A w = xi.next();
while(xi.hasNext()) {
A x = xi.next();
if(w.compareTo(x) < 0)
w = x;
}
return w;
}
}
//.............................类型擦除后........................................
// 代码示例 3
class Collections {
public static Comparable max(Collection xs) {
Iterator xi = xs.iterator();
Comparable w = (Comparable) xi.next();
while (xi.hasNext()) {
Comparable x = (Comparable) xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
// 代码示例 4
final class Test {
public static void main (String[] args) {
LinkedList<NumericValue> numberList = new LinkedList<NumericValue> ();
numberList.add(new NumericValue((byte)0));
numberList.add(new NumericValue((byte)1));
NumericValue y = Collections.max( numberList );
}
}
//.............................类型擦除后........................................
// 代码示例 4
final class Test {
public static void main (String[ ] args) {
LinkedList numberList = new LinkedList();
numberList.add(new NumericValue((byte)0)); ,
numberList.add(new NumericValue((byte)1));
NumericValue y = (NumericValue) Collections.max( numberList );
}
}
第一个泛型类被擦除后, A被替换为最左边界 Object。由于Comparable是一个泛型接口,所以Comparable的类型参数NumericValue被擦除掉并将相关参数置换为Object,但是这直接导致NumericValue没有实现接口(重写)Comparable的compareTo(Object that)方法,于是编译器自动添加了一个桥方法(由编译器在编译时自动添加)。第二个示例中限定了类型参数的边界,A必须为Comparable的子类,按照类型擦除的过程,先将所有的类型参数替换为最左边界Comparable,得到最终的擦除后结果。
六. 泛型带来的问题及解决方法
1、类型检查所针对的对象
public class Test10 {
public static void main(String[] args) {
ArrayList<String> arrayList1=new ArrayList();
arrayList1.add("1"); // 编译通过
arrayList1.add(1); // 编译错误
String str1=arrayList1.get(0); // 返回类型就是 String
ArrayList arrayList2=new ArrayList<String>();
arrayList2.add("1"); // 编译通过
arrayList2.add(1); // 编译通过
Object object=arrayList2.get(0); // 返回类型就是 Object
new ArrayList<String>().add("11"); // 编译通过
new ArrayList<String>().add(22); // 编译错误
String string=new ArrayList<String>().get(0); // 返回类型就是 String
}
}
因此我们可以得出结论:类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
2、所有动作都发生在边界处(对传递进来的值,编译器进行额外的检查;对真正传递出去的值,编译器自动插入的转型)
因为类型擦除的问题,所以所有的泛型类型最后都会被替换为原始类型。这样就引起了一个问题,既然都被替换为原始类型,那么为什么我们在获取的时候,不需要进行强制类型转换呢?先看下面非泛型示例:
// 代码片段1
public class SimpleHolder {
private Object obj;
public void setObj(Object obj) {
this.obj = obj;
}
public Object getObj() {
return obj;
}
public static void main(String[] args) {
SimpleHolder holder = new SimpleHolder();
holder.setObj("Item");
String s = (String)holder.getObj();
}
}
反编译这个类,得到下面代码片段:
public void setObj(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: putfield #2; //Field obj:Ljava/lang/Object;
5: return
public java.lang.Object getObj();
Code:
0: aload_0
1: getfield #2; //Field obj:Ljava/lang/Object;
4: areturn
public static void main(java.lang.String[]);
Code:
0: new #3; //class SimpleHolder
3: dup
4: invokespecial #4; //Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #5; //String Item
11: invokevirtual #6; //Method setObj:(Ljava/lang/Object;)V
14: aload_1
15: invokevirtual #7; //Method getObj:()Ljava/lang/Object;
18: checkcast #8; //class java/lang/String
21: astore_2
22: return
现将泛型应用到上述代码,如下:
// 代码片段 2
public class GenericHolder<T> {
private T obj;
public void setObj(T obj) {
this.obj = obj;
}
public T getObj() {
return obj;
}
public static void main(String[] args) {
GenericHolder<String> holder = new GenericHolder<String>();
holder.setObj("Item");
String s = holder.getObj();
}
}
反编译这个类,得到下面代码片段:
public void setObj(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: putfield #2; //Field obj:Ljava/lang/Object;
5: return
public java.lang.Object getObj();
Code:
0: aload_0
1: getfield #2; //Field obj:Ljava/lang/Object;
4: areturn
public static void main(java.lang.String[]);
Code:
0: new #3; //class GenericHolder
3: dup
4: invokespecial #4; //Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #5; //String Item
11: invokevirtual #6; //Method setObj:(Ljava/lang/Object;)V
14: aload_1
15: invokevirtual #7; //Method getObj:()Ljava/lang/Object;
18: checkcast #8; //class java/lang/String
21: astore_2
22: return
在上述应用泛型的代码中,将:
String s = holder.getObj();
替换为:
holder.getObj();
反编译后,有代码片段:
public void setObj(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: putfield #2; //Field obj:Ljava/lang/Object;
5: return
public java.lang.Object getObj();
Code:
0: aload_0
1: getfield #2; //Field obj:Ljava/lang/Object;
4: areturn
public static void main(java.lang.String[]);
Code:
0: new #3; //class GenericHolder
3: dup
4: invokespecial #4; //Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #5; //String Item
11: invokevirtual #6; //Method setObj:(Ljava/lang/Object;)V
14: aload_1
15: invokevirtual #7; //Method getObj:()Ljava/lang/Object;
18: pop
19: return
}
首先,代码片段 1 和代码片段 2 二者所产生的字节码是相同的。看第15,它调用的是getObj()方法,返回值是Object,说明类型擦除了。然后第18,它做了一个checkcast操作,即检查类型#8, 在上面找#8引用的类型,它是一个String类型,即作String类型的强转。所以不是在get方法里强转的,是在你调用的地方强转的。对进入setObj()的类型进行检查是不需要的,因为这将由编译器执行。而对从getObj()返回的值进行转型仍旧是需要的,但这与你自己必须执行的操作是一样的–此处它将由编译器自动插入。也就是说,在泛型中,所有动作都发生在边界处,对传递进来的值进行额外的编译器检查,并由编译器自动插入对传递出去的值的转型。
其次,在未将 getObj() 的值赋给String时,由代码片段可知,编译器并未自动插入转型代码,可见所谓编译器自动插入对传递出去的值的转型的前提条件是:其必须是真正传递出去,即必须赋值给引用。(当然,虽然 getObj() 的返回值的类型是 Object, 但是其实质上是一个 String, 因此直接进行操作 “ getObj() instanceof String ”时,返回值也是 true。)再看一段代码:
public class GenericArray<T> {
private T[] array;
public GenericArray(int sz) {
array = (T[]) new Object[sz];
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return array[index];
}
public T[] rep() { return array; }
public static void main(String[] args) {
GenericArray<Integer> gai = new GenericArray<Integer>(10);
gai.put(0, new Integer(4));
gai.get(0);
Integer i = gai.get(0);
// This causes a ClassCastException:
Integer[] ia = gai.rep();
// This is OK:
Object[] oa = (Object[])gai.rep();
}
}
反编译得代码段:
public class GenericArray extends java.lang.Object{
public GenericArray(int);
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: aload_0
5: iload_1
6: anewarray #2; //class java/lang/Object
9: checkcast #3; //class "[Ljava/lang/Object;"
12: putfield #4; //Field array:[Ljava/lang/Object;
15: return
public void put(int, java.lang.Object);
Code:
0: aload_0
1: getfield #4; //Field array:[Ljava/lang/Object;
4: iload_1
5: aload_2
6: aastore
7: return
public java.lang.Object get(int);
Code:
0: aload_0
1: getfield #4; //Field array:[Ljava/lang/Object;
4: iload_1
5: aaload
6: areturn
public java.lang.Object[] rep();
Code:
0: aload_0
1: getfield #4; //Field array:[Ljava/lang/Object;
4: areturn
public static void main(java.lang.String[]);
Code:
0: new #5; //class GenericArray
3: dup
4: bipush 10
6: invokespecial #6; //Method "<init>":(I)V
9: astore_1
10: aload_1
11: iconst_0
12: new #7; //class java/lang/Integer
15: dup
16: iconst_4
17: invokespecial #8; //Method java/lang/Integer."<init>":(I)V
20: invokevirtual #9; //Method put:(ILjava/lang/Object;)V
23: aload_1
24: iconst_0
25: invokevirtual #10; //Method get:(I)Ljava/lang/Object;
28: pop
29: aload_1
30: iconst_0
31: invokevirtual #10; //Method get:(I)Ljava/lang/Object;
34: checkcast #7; //class java/lang/Integer
37: astore_2
38: aload_1
39: invokevirtual #11; //Method rep:()[Ljava/lang/Object;
42: checkcast #12; //class "[Ljava/lang/Integer;"
45: astore_3
46: aload_1
47: invokevirtual #11; //Method rep:()[Ljava/lang/Object;
50: checkcast #3; //class "[Ljava/lang/Object;"
53: astore 4
55: return
}
结合上面的结论,仔细观察反编译后代码中checkcast都用在什么地方,加深对边界就是发生动作的地方和自动转型发生在调用处(需要检验两种类型时)的理解。
25显式调用后,直接pop,而31显示在调用处,还要进行 checkcast 操作;
由于类型擦除,操作39之后,进行 checkcast 操作,强转为 Ljava.lang.Integer ,但是由代码【 array = (T[]) new Object[sz]; 】可知,其 new 的是 Object 数组,是不可能成功强转到 Integer 数组的,就像 Object 对象不能成功强转到 Integer 对象一样,会在运行时抛出 ClassCastException 异常;
由于类型擦除,操作47之后,进行 checkcast 操作,由于 rep() 返回的即为 Object 数组,而其要赋给的引用也是 Object[] ,因此不会抛出任何异常。
3、类型擦除与多态的冲突及其解决办法
先看两段代码:
// 第一段代码
public class Pair<T> {
private T first;
private T second;
public Pair(T first, T second){
this.first = first;
this.second = second;
}
public void setFirst(T first){
this.first = first;
}
public T getFirst(){
return first;
}
public void setSecond(T second){
this.second = second;
}
public T getSecond(){
return second;
}
}
// 第二段代码
public class DateInterval extends Pair<Date> { // 时间间隔类
public DateInterval(Date first, Date second){
super(first, second);
}
@Override
public void setSecond(Date second) {
super.setSecond(second);
}
@Override
public Date getSecond(){
return super.getSecond();
}
public static void main(String[] args) {
DateInterval interval = new DateInterval(new Date(), new Date());
Pair<Date> pair = interval; //超类,多态
Date date = new Date(2000, 1, 1);
System.out.println("原来的日期:"+pair.getSecond());
System.out.println("set进新日期:"+date);
pair.setSecond(date);
System.out.println("执行pair.setSecond(date)后的日期:"+pair.getSecond());
}
}
原本子类重写父类的方法,无可非议。但是泛型类的类型擦除造成了一个问题,Pair的原始类型中存在方法:
public void setSecond(Object second);
DateInterval 中的方法:
public void setSecond(Date second);
我们的本意是想重写父类Pair中的setSecond方法,但是从方法签名上看,这完全是两个不同的方法,类型擦除与多态产生了冲突。而实际情况呢?运行DateInterval的main方法,我们看到public void setSecond(Date second)的确重写了public void setSecond(Object second)方法。这是如何做到的呢?
使用 Java 类分析器 对其进行分析,结果:
public class DateInterval extends Pair{
// 构造器
public DateInterval(java.util.Date, java.util.Date);
// 方法
public void setSecond(java.util.Date);
public volatile void setSecond(java.lang.Object); // 方法 1
public java.util.Date getSecond( ); // 方法 2
public volatile java.lang.Object getSecond( ); // 方法 3,它难道不会和方法 2 冲突?
public static void main(java.lang.String[]);
}
方法1和方法3是我们在源码中不曾定义的,它肯定是由编译器生成的。这个方法称为 桥方法(bridge method),真正覆写超类方法的是它。语句pair.setSecond(date)实际上调用的是方法1[public volatile void setSecond(Object)],通过这个方法再去调用public void setSecond(Date)。这个桥方法的实际内容是:
public void setSecond(Object second){
this.setSecond((java.util.Date) second);
}
这样的结果就符合面向对象中多态的特性了,实现了方法的动态绑定。但是,这样的做法给我们带来了一种错觉,就认为public void setSecond(Date)覆写了泛型类的public void setSecond(Object)【其实也不是重写,二者方法参数都不同】,如果我们在DateInterval中增加一个方法:
public void setSecond(Object obj){
System.out.println("覆写超类方法!");
}
编译器会报如下错误:Name clash: The method setSecond(Object) of type DateInter has the same erasure as setSecond(T) of type Pair but doesn’t override it.即,同一个方法不能被重写两次。
为了实现多态,我们知道方法3也是由编译器生成的桥方法。方法擦除带来的第二个问题就是:由编译器生成的桥方法 public volatile java.lang.Object getSecond() 方法和 public java.util.Date getSecond() 方法,从方法签名的角度看是两个完全相同的方法,它们怎么可以共存呢? 如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情。
4、泛型类型变量不能是基本数据类型
类型参数不能是基本类型。也就是说,没有ArrayList,只有ArrayList。因为当类型擦除后,ArrayList的原始类型变为Object,但是Object类型不能存储double值,只能引用Double的值。
5、转型和警告
使用带有泛型类型参数的转型或 instanceof 不会有任何效果,例如:
class FixedSizeStack<T> {
private int index = 0;
private Object[] storage;
public FixedSizeStack(int size) {
storage = new Object[size];
}
public void push(T item) {
storage[index++] = item;
}
public T pop() {
// Warnning: Unchecked cast from Object to T
return (T) storage[--index];
}
}
public class GenericCast {
public static final int SIZE = 10;
public static void main(String[] args) {
FixedSizeStack<String> strings = new FixedSizeStack<String>(SIZE);
for (String s : "A B C D E F G H I J".split(" "))
strings.push(s);
for (int i = 0; i < SIZE; i++) {
String s = strings.pop();
System.out.print(s + " ");
}
}
}
由于擦除的原因,T 被擦除到它的第一个边界 Object,因此pop()实际上只是将Object转型为Object。换句话说,pop()方法实际上并没有执行任何转型。
6、任何在运行时需要知道确切类型信息的操作都将无法工作
1、instanceof 操作的右操作数不能带有泛型类型参数;
2、new 操作 : 可以 new 泛型类型(eg: ArrayList,…),但不能 new 泛型参数(T,…);
3、泛型数组 : 除非使用通配符,不可以创建带有泛型类型参数的数组(若需要收集参数化类型对象,可以直接使用 ArrayList:ArrayList>最安全且有效。);
4、转型 : 带有泛型类型参数的转型不会有任何效果;
例如:

关于由类型擦除引起的 instance of T,new T 和创建数组T 等问题,可以引入类型标签Class来解决,例如:
class Building {}
class House extends Building {}
public class ClassTypeCapture<T> {
Class<T> kind;
public ClassTypeCapture(Class<T> kind) {
this.kind = kind;
}
public boolean f(Object arg) {
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(Building.class);
System.out.println(ctt1.f(new Building())); // true
System.out.println(ctt1.f(new House())); // true
ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class);
System.out.println(ctt2.f(new Building())); // true
System.out.println(ctt2.f(new House())); // true
}
}
7、实现参数化接口
一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口,例如:
public Person implements Comparable<Person>{ ... } // OK
class HonorPerson extends Person implements Comparable<HonorPerson>{ ... } // Error
HonorPerson 类不能编译,因为擦除会将 Comparable和Comparable 简化为相同的接口Comparable, 上面的代码意味着重复实现相同的接口。但是,下面的代码可以通过编译:
public Person implements Comparable{ ... } // OK
class HonorPerson extends Person implements Comparable{ ... } // OK
这种差别在于:编译器对泛型的特别处理方式。
8、异常中使用泛型的问题
由于类型擦除的原因,将泛型应用于异常是非常受限的。catch 语句不能捕获泛型类型的异常,因为在编译期和运行时都必须知道异常的确切类型。
(1). 不能抛出也不能捕获泛型类的对象
事实上,泛型类扩展Throwable都不合法(Exception是Throwable的子类)。例如:下面的定义将不会通过编译:
public class Problem<T> extends Exception{......}
为什么不能扩展Throwable,因为异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉,那么,假设上面的编译可行,那么,再看下面的定义:
try{
}catch(Problem<Integer> e1){
...
}catch(Problem<Number> e2){
...
}
在运行时,类型信息被擦除后,那么两个地方的catch都变为原始类型Object,那么也就是说,这两个地方的catch变的一模一样,就“相当于”下面的这样:
try{
}catch(Problem<Object> e1){
...
}catch(Problem<Object> e2){
...
}
这当然就是不行的, 就好像catch了两个一模一样的普通异常,编译器就不能通过编译一样。
(2). 不能再catch子句中使用泛型变量
例如:
public static <T extends Throwable> void doWork(Class<T> t){
try{
...
}catch(T e){ //编译错误
...
}
}
因为泛型信息在编译的时候已经变为原始类型,也就是说上面的 T 会变为原始类型 Throwable,那么如果可以再catch子句中使用泛型变量,那么,下面的定义呢:
public static <T extends Throwable> void doWork(Class<T> t){
try{
...
}catch(T e){ //编译错误
...
}catch(IndexOutOfBounds e){
}
}
根据异常捕获的原则,一定是子类在前面,父类在后面,那么上面就违背了这个原则。所以java为了避免这样的情况,禁止在catch子句中使用泛型变量。
(3). 类型变量可以使用在异常声明中
public static<T extends Throwable> void doWork(T t) throws T{
try{
...
}catch(Throwable realCause){
t.initCause(realCause);
throw t;
}
}
此时,虽然 T 也会被擦除为 Throwable,但由于用在声明中,因此是合法的。
9、类型擦除后的冲突
当泛型类型被擦除后,创建条件不能产生冲突,例如:
class Pair<T> {
public boolean equals(T value) {
return null;
}
}
考虑Pair<>:
public boolean equals(T value){}
擦除后变为:
boolean equals(Object)
这与 Object.equals 方法是冲突的!当然,补救的办法是重新命名引发错误的方法。
10、动态类型安全
先看以下代码:
public class CheckedList {
@SuppressWarnings("unchecked")
static void oldStyleMethod(List probablyDogs) { // 原生List
probablyDogs.add(new Cat());
}
public static void main(String[] args) {
List<Dog> dogs1 = new ArrayList<Dog>();
oldStyleMethod(dogs1); // Quietly accepts a Cat
List<Dog> dogs2 = Collections.checkedList(new ArrayList<Dog>(), Dog.class);
try {
oldStyleMethod(dogs2); // Throws an exception
} catch(Exception e) {
System.out.println(e);
}
// Derived types work fine:
List<Pet> pets = Collections.checkedList(
new ArrayList<Pet>(), Pet.class);
pets.add(new Dog());
pets.add(new Cat());
}
}
/** Output:
java.lang.ClassCastException: Attempt to insert class typeinfo.pets.Cat
element into collection with element type class typeinfo.pets.Dog
*/
使用 Collections 的静态方法:checkedCollection(), checkedList(), checkedMap(), checkedSet(),checkedSortedMap() 和 checkedSortedSet() 可以在运行时便知道罪魁祸首在哪里,而不必等到将对象从容器中取出时。
七. 小结
泛型本意即为类型参数化,提供了更为广泛的表达能力,需要注意两点:
1、泛型只存在于编译期阶段,该阶段完成了针对引用的类型检查,将泛型参数替换为非泛型上界并由编译器在边界处自动插入转型;
2、每个泛型类只生成唯一的一份目标代码,该泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换。
还没有评论,来说两句吧...