Java泛型擦除的补偿
public class Main<T> {
public T makeArray() {
// error: Type parameter 'T' cannot be instantiated directly
return new T;
}
}
我们无法在泛型内部创建一个T类型的对象,因为在运行时T仅仅是个占位符,并没有真实的类型信息,实际上,除了new表达式之外,instanceof操作和转型(会收到警告)在泛型内部都是无法使用的,而造成这个的原因就是之前讲过的编译器对类型信息进行了擦除。如果编译时不报错,那么将会在运行时出现异常。故在编译时即报错。
为什么还要使用泛型:
public class Main<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Main<String> m = new Main<String>();
m.set("findingsea");
String s = m.get();
System.out.println(s);
}
}
/* Output
findingsea
*/
虽然有类型擦除的存在,使得编译器在泛型内部其实完全无法知道有关T的任何信息,但是编译器可以保证重要的一点:内部一致性,也是我们放进去的是什么类型的对象,取出来还是相同类型的对象,这一点让Java的泛型起码还是有用武之地的。
代码片段四展现就是编译器确保了我们放在t上的类型的确是T(即便它并不知道有关T的任何类型信息)。这种确保其实做了两步工作:
set()处的类型检验
get()处的类型转换
这两步工作也成为边界动作。
解决 —— 擦除的补偿
如上看到的,但凡是涉及到确切类型信息的操作,在泛型内部都是无法共工作的。那是否有办法绕过这个问题来编程,答案就是显示地传递类型标签。
public class Main<T> {
public T create(Class<T> type) {
try {
return type.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
Main<String> m = new Main<String>();
String s = m.create(String.class);
}
}
上述代码展示了一种用类型标签生成新对象的方法,但是这个办法很脆弱,因为这种办法要求对应的类型必须有默认构造函数,遇到Integer类型的时候就失败了,而且这个错误还不能在编译器捕获。
进阶的方法可以用限制类型的显示工厂和模板方法设计模式来改进这个问题,具体可以参见《Java编程思想 (第4版)》P382。
public class Main<T> {
public T[] create(Class<T> type) {
return (T[]) Array.newInstance(type, 10);
}
public static void main(String[] args) {
Main<String> m = new Main<String>();
String[] strings = m.create(String.class);
}
}
上述代码展示了对泛型数组的擦除补偿,本质方法还是通过显示地传递类型标签,通过Array.newInstance(type, size)来生成数组,同时也是最为推荐的在泛型内部生成数组的方法。