繁琐的语法和泛型擦除原理
Java泛型的语法有很多看起来毫无意义的规则和写法,例如:
<? extends Fruit>
<T extends Fruit>
(T[])new Object[size]
// ...
这些本质上都是由于Java泛型机制的实现原理——擦除。
所谓的擦除:
- 所有的泛型在编译之后都会被擦除,替换为它们的非泛型上界,例如List,其实实际的类型是Object
- Java的泛型指在编译之前多了额外的检查
这看起来以一种很轻量优雅的方式实现的泛型,但实际上带来的诸多不便,包括功能上的损失,有时候不像直觉一样满足期望。特别是跟类似c++这种真正的泛型对比起来。
之所以要这么设计,本质上是考虑了Java的向后兼容性,设计团队在对泛型进行研究决策后认为擦除是唯一能满足需求的实现方式——允许Java向早期发布的软件兼容,最终的编译之后的代码毫无区别,软件的使用者无法感知泛型的存在。
这也就给后续增加了很多补偿的语法。
泛型
泛型的核心要义是:
- Java泛型只在编译前检查
- Java泛型在运行时会被完全擦除为原生类型,在运行时,它压根无法得知任何泛型信息
- Java泛型的语法,编码规则尽管看起来非常像那么回事,仿佛在告诉我们它就是泛型。但是我们要时刻提醒自己:不,它只是一个Object
泛型补偿
正由于Java泛型会被完成擦除,所以新建泛型对象,新建泛型数组,instance of等等,这些都是无法编译通过的,所以说Java的泛型是受限的,有时候也会令人失望,如果想按照直觉对他们进行使用的话。
// 编译失败
new T();
new T[10];
if(T instance of Fruit){
}
因而也演变出一系列补偿的语法。
对象创建
Java泛型无法新建对象,所以很多时候我们都要给出一个工厂,用于创建对象。
使用class对象实现工厂
使用类对象可以通过反射新建对象,这也是Java泛型中经常需要传入xxx.class的原因。
// 泛型补偿:使用类对象工厂
class ClassAsFactory<T> {
private Class<T> type;
public ClassAsFactory(Class<T> type) {
this.type = type;
}
public T create() {
try {
return type.newInstance();
} catch (Exception e) {
throw new RuntimeException();
}
}
}
这种方式的缺点是,当该泛型对象指定的类型没有默认构造器,就会运行失败,例如Integer。
使用自定义对象工厂
Java的开发团队更推荐使用自定义工厂。
// 泛型补偿:使用自定义工厂
interface Factory<T> {
T create();
}
class IntegerFactory implements Factory<Integer> {
@Override
public Integer create() {
return new Integer(0);
}
}
数组创建
上文中也提到