泛型编程的局限性
下面介绍泛型编程的局限性,他们中大多数都是因为类型擦除引起的。
类型参数不能为原始类型
你无法使用原始类型初始化类型参数,比如,你没有办法使用Pair<double>,只有Pair<Double>。究其原因,是因为类型擦除之后,泛型类型被Object或者边界类型代替,但是double并不是从Object继承而来。
这一点通常非常难受,但并不致命,因为你可以使用包装类。
运行时类型检查只对类型擦除后的类有效
Java虚拟机内部没有泛型类,因此当你在运行时进行类型检查时,指挥检查泛型类,而不会检查泛型类型。比如
if(a instanceof Pair<String>) // 和a instanceof Pair是一样的
只会检查a是不是Pair类型,下面的测试也是一样
if(a instanceof Pair<T>) // T被忽略
为了让你避免发生类型转换的错误,每次你对泛型类使用instanceof或者类型转换时,编译器会给出警告。
由于同样的原因,getClass函数会返回原始类型。比如
Pair<String> stringPair = ...;
Pair<Employee> employeePair = ...;
if(stringPair.getClass() == employeePair.getClass()) // 相等
最后的判断会返回true,因为他们都返回Pair.Class。
没有办法抛出泛型类对象
你没有办法抛出或捕获泛型类对象。事实上,你没有办法让泛型类继承自Throwable。比如,下面的定义会导致编译错误。
public class Problem<T> extends Throwable{/*...*/} // 错误,不能继承自Throwable。
在catch语句中,你也不能使用类型变量
public static <T extends Throwable> void doWork(Class<T> t)
{
try{
doWork
}
catch(T e){
Logger.global.info(...);
}
}
但是,在catch语句内部,你可以使用泛型变量。比如下面的方法是合法的
public static <T extends Throwable> void doWork(T t) throws T // OK
{
try
{
dowork
}
catch(Throwable realCause){
t.initCause(reslCause);
throw r;
}
}
泛型类对象没有办法构建数组
你不能定义泛型类对象的数组
Pair<String>[] table = new Pair<String>[10]; // 错误
有什么问题呢?类型擦除之后,table的类型是Pair[]类型,你可以将它转换成为Object[]类型。
Object[] objectArray = table;
当向数组中插入错误的数据类型时,编译器会报错,比如
objectArray[0] = "Hello"; // 错误,数组类型是Pair
但是类型擦除之后,类型变量不起作用,所以下面的语句也是合法的
objectArray[0] = new Pair<Employee>();
为了避免这个错误,编译器不允许定义泛型类数组。
不能使用初始化类型变量
你不能使用类型变量定义对象,下面的代码是不合法的
public Pair(){ first = new T(); second = new T(); } // 报错
类型擦除之后,T将会被替代为Object,你不会想调用new Object();
你可能会想到使用反射机制定义变量。但是这也很复杂,你不能使用下面的语句
first = T.class.newInstance(); // 报错
表达式T.class是不合法的,相反,你需要设计API,处理Class对象。比如
public static <T> Pair<T> makePair(Class<T> cl)
{
try
{
return new Pair<T>(cl.newInstance(),cl.newInstance())
}
catch (Exception e)
{
return null;
}
}
上面的方法应该这样调用
Pair<String> p = Pair.makePair(String.class);
Class对象本身也是泛型的,比如,String.class是Class<String>的一个对象。所以makePair函数可以推断他创建的对象的类型。
在静态函数中使用类型变量是不合法的
在静态方法中不允许使用类型变量。
public class Singleton<T>
{
public static T getSingleInstance() // 错误
{
if(singleInstance == null )...
}
private static T singleInstance; // Error
}
如果上面的代码合法,那么进过类型擦除后,只会有一个Singleton类型,即使设置不同的T,他们也指向同一个singleInstance。这会导致很多问题。
了解类型擦除后发生的冲突
通常,你不能实现一个泛型类,如果他集成自一个接口和一个类,而他们继承自同样的接口或类。这会导致类型擦除之后过渡函数的生成发生冲突。