转自:
http://www.myexception.cn/mobile/692129.html
向作者致谢!
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
大多数这些需要注意的问题,都是由类型擦除引起的。
一、基本类型
不能用类型参数替换基本类型。就比如,没有ArrayList<double>,只有ArrayList<Double>。因为当类型擦除后,ArrayList的原始类型变为Object,但是Object类型不能存储double值,只能引用Double的值。
二、运行时类型查新
举个例子:
ArrayList<String> arrayList=new ArrayList<String>();
因为类型擦除之后,ArrayList<String>只剩下原始类型,泛型信息String不存在了。
那么,运行时进行类型查询的时候使用下面的方法是错误的
if( arrayList instanceof ArrayList<String>)
java限定了这种类型查询的方式
if( arrayList instanceof ArrayList<?>)
三、异常
1、不能抛出也不能捕获泛型类的对象。事实上,泛型类扩展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两个一模一样的普通异常,不能通过编译一样:
try{ }catch(Exception e1){ 。。 }catch(Exception e2){//编译错误 ...
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){ } }
,根据异常捕获的原则,一定是子类在前面,父类在后面,那么上面就违背了这个原则。即使你在使用该静态方法的使用T是ArrayIndexOutofBounds,在编译之后还是会变成Throwable,ArrayIndexOutofBounds是IndexOutofBounds的子类,违背了异常捕获的原则。所以java为了避免这样的情况,禁止在catch子句中使用泛型变量。
但是在异常声明中可以使用类型变量。下面方法是合法的。
public static<T extends Throwable> void doWork(T t) throws T{ try{ ... }catch(Throwable realCause){ t.initCause(realCause); throw t; } }
上面的这样使用是没问题的。
四、数组
不能声明参数化类型的数组。如:
Pair<String>[] table = newPair<String>(10); //ERROR
这是因为擦除后,table的类型变为Pair[],可以转化成一个Object[]。
Object[] objarray =table;
数组可以记住自己的元素类型,下面的赋值会抛出一个ArrayStoreException异常。
objarray ="Hello"; //ERROR
对于泛型而言,擦除降低了这个机制的效率。下面的赋值可以通过数组存储的检测,但仍然会导致类型错误。
objarray =new Pair<Employee>();
提示:如果需要收集参数化类型对象,直接使用ArrayList:ArrayList<Pair<String>>最安全且有效。
五、泛型类型的实例化
不能使用像new T(...),new T[...],T.class这样的表达式
不能实例化泛型类型。如,
first = newT(); //ERROR
是错误的,类型擦除会使这个操作做成new Object()。
可以使用Class.newInstance()方法:
cl.newInstanc();
不能建立一个泛型数组。
public<T> T[] minMax(T[] a){ T[] mm = new T[2]; //ERROR ... }
类似的,擦除会使这个方法总是构靠一个Object[2]数组。但是,可以用反射构造泛型对象和数组。
利用反射,调用Array.newInstance:
publicstatic <T extends Comparable> T[]minmax(T[] a) { T[] mm == (T[])Array.newInstance(a.getClass().getComponentType(),2); ... // 以替换掉以下代码 // Obeject[] mm = new Object[2]; // return (T[]) mm; }
六、静态上下文
不能在静态域或者方法中引用类型变量。
public class Test<M> { public static void test(M m) { //error } }
这句话的错误提示是无法在静态上下文中引用非静态类 M.
因为test方法是静态的可以用类名直接调用,而M只有在类Test创建对象的时候才会明确.
test静态方法存在的时候还没明确M是什么.
所以静态方法不可以访问类上定义的泛型.
如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上.
可以改成:
public class Test { public static <M> void test(M m) { //right } }
七、类型擦除后的冲突
1、
当泛型类型被擦除后,创建条件不能产生冲突。如果在Pair类中添加下面的equals方法:
class Pair<T> { public boolean equals(T value) { return null; } }
考虑一个Pair<String>。从概念上,它有两个equals方法:
booleanequals(String); //在Pair<T>中定义
boolean equals(Object); //从object中继承
但是,这只是一种错觉。实际上,擦除后方法
booleanequals(T)
变成了方法 booleanequals(Object)
这与Object.equals方法是冲突的!当然,补救的办法是重新命名引发错误的方法。
2、
泛型规范说明提及另一个原则“要支持擦除的转换,需要强行制一个类或者类型变量不能同时成为两个接口的子类,而这两个子类是同一接品的不同参数化。”
下面的代码是非法的:
class Calendar implements Comparable<Calendar>{ ... }
class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar>{...} //ERROR
GregorianCalendar会实现Comparable<Calender>和Compable<GregorianCalendar>,这是同一个接口的不同参数化实现。
这一限制与类型擦除的关系并不很明确。非泛型版本:
class Calendar implements Comparable{ ... }
class GregorianCalendar extends Calendar implements Comparable{...} //ERROR
是合法的。
有可能与合成的桥方法产生冲突有关:
public int compareTo(Object other) {return compareTo((X) other);}
方法体产生冲突,X应是Calender还是GregorianCalendar,不确定
八、泛型中 参数化类型不考虑继承关系
ArrayList<String> arrayList1=new ArrayList<Object>();//编译错误 ArrayList<Object> arrayList1=new ArrayList<String>();//编译错误