最近在学习泛型,在用泛型的时候有3个不能做的事情:一是不能使用instanceof,二是不能new对象,三是不能创建泛型数组。下面我想记录一下我对这3点的理解,尤其是第三点。
不能使用instanceof
这一点我觉得还是比较好理解的,因为擦除的原因,泛型会被擦除到类型上限。在这种情况下使用instanceof是没有什么意义的,因为并不知道T的实际类型。就是说泛型在传入参数的时候编辑器会检查类型,保证类型的正确,而在取值赋值的时候会强制转型。而在其他地方使用,T会变成它的类型上限,如果没写类型上限,那就是Object,这样的话使用instanceof就没什么用处了。
编译器的解释很清楚,类型信息在运行时会被擦除。解决办法就是使用T的类型上限代替T,而这里就是Object。
不能创建对象
可以用泛型声明引用,但是却不能使用泛型来创建对象。原因是因为你不能保证T一定会有你写的那个构造方法。比如T t = new T()的时候你是不能保证传入具体的类型信息的类的时候会有无参的构造方法的。
解决办法是通过方法返回对象,而在具体能够得知类型信息的类中再来完成这个方法。
1 public abstract class Test<T> { 2 T t; 3 4 public Test(){ 5 t = create(); 6 } 7 8 abstract T create(); 9 } 10 11 class Test_1 extends Test<String>{ 12 @Override 13 String create() { 14 return ""; 15 } 16 17 }
简单来说就是把创建泛型对象的时间延迟到了能够确定参数类型的时候。
不能创建泛型数组
为什么不能创建泛型数组呢?
1 public class GenericArrayTest<T> { 2 T t; 3 public T get(){ 4 return t; 5 } 6 public void test(){ 7 GenericArrayTest<String>[] arr = new GenericArrayTest<String>[10]; 8 } 9 }
如上面的代码,因为类型擦除以后这个对象数组里面存的是原生GenericArrayTest对象,T是Object类型的,GenericArrayTest<String>和GenericArrayTest<Integer>是一样的,那么如果有Obejct[] objArr这个引用,它可以被赋值为arr,但是你不能保证objArr不会去存储GenericArrayTest<Integer>的对象。那么结果就是arr这个数组里会有GenericArrayTest<String>又有GenericArrayTest<Integer>。然后你取得arr数组中的GenericArrayTest元素,再取T类型的对象,也就是String的对象的时候,编译器会帮你强行转型。意思就是arr[0].get()这句话返回的是String类型的对象而不是Object,因为编译器帮你转型了。但是因为arr里可能会有GenericArrayTest<Integer>,取得数组元素再调用get方法返回的是一个Integer的对象,被强制转型成String会报错。所以是不能创建泛型数组的。
这样就完了吗?
然而并没有。泛型和数组搭配在一起有一些特别迷惑人地方。我想举一个我在CSDN上看到的问题贴里的例子,大家的回答我都不是特别满意。我想说说我的理解。
1 public class SimpleCollection<T> { 2 public static void main(String[] args) { 3 // Object[] o = new Object[10]; 4 // String[] s = (String[]) o;// 运行报错:Exception in thread "main" 5 // java.lang.ClassCastException: 6 // [Ljava.lang.Object; cannot be cast to 7 // [Ljava.lang.String; 8 SimpleCollection<String> si = new SimpleCollection<String>(); 9 si.method(); 10 11 } 12 public void method() { 13 Object[] o = new Object[10]; 14 T[] t = (T[]) o;// Type safety: Unchecked cast from Object[] 编译警告,运行正常 15 } 16 17 }
提问者的问题就是为了第4行会报错,而用了泛型,调用method却不会报错。
我是这么理解的:
首先为什么第四行会报错?这是一个转型的问题,父类引用如果指向的是父类对象,那么强制转型编译是不会错的,但是执行的时候会报ClassCastException。而如果父类引用指向的是子类对象,那么转型就没问题。所以
Object[] o = new String[10];
String[] s = (String[]) o;
这样使用是不错报错的。
那么为什么使用泛型就没有报错?因为类型擦除。T会被擦除为Object。所以13,14行相当于
Object[] o = new String[10];
Object[] t = o;
这运行可能会报错吗?显然不会
那是不是意味着可以用泛型来帮助转型数组呢?
显然不是,我觉得在这个例子中用不用泛型几乎没有影响,或者说没有用到泛型的优势吧。
我们来修改一下这个例子。
1 public class SimpleCollection<T> { 2 public static void main(String[] args) { 3 //Object[] o = new Object[10]; 4 //String[] s = (String[]) o;// 运行报错:Exception in thread "main" 5 // java.lang.ClassCastException: 6 // [Ljava.lang.Object; cannot be cast to 7 // [Ljava.lang.String; 8 SimpleCollection<String> si = new SimpleCollection<String>(); 9 si.method(); 10 String[] arr = si.method2(); 11 12 } 13 public void method() { 14 Object[] o = new Object[10]; 15 T[] t = (T[]) o;// Type safety: Unchecked cast from Object[] 编译警告,运行正常 16 } 17 18 public T[] method2() { 19 Object[] o = new Object[10]; 20 T[] t = (T[]) o; 21 return t; 22 } 23 }
现在我们增加了一个method2方法。用了泛型的好处就是编译器会帮我们转型,不用自己转型,并且能保证类型正确。
这里编译没有任何错误,但是到第10行会报错。原因和直接转型的第四行是一样的道理。所以说要特别小心泛型赋值和传入参数时候的错误。method()没有错误只是因为没有赋值,没有转型而已。并不意味着使用泛型可以安全的转型数组。
还有什么值得说
还有一些小细节值得说,并不是关于泛型的,而是关于数组的,是我在测试小例子的时候发现的。我觉得很有趣,值得一提。
String[] 和 Object[] 到底是不是父子类的关系?
我感觉答案是既是又不是。
是:那是因为new String[2] instanceof Object[]返回的是true
不是:那是因为String[].class.getSuperclass()返回的是java.lang.Object,而Object[].class是[Ljava.lang.Object;从这点上看好像他们并没有什么关系。
这真的有点令人迷惑。