Java泛型(5):擦除与补偿
先看一个例子:
Class<?> c1 = new ArrayList<String>().getClass(); Class<?> c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2); // true
虽然泛型类的参数不同,但是结果却是TRUE。这是因为在泛型代码内部,无法获得任何有关泛型参数类型的信息。
Java泛型是通过擦除来实现的。这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的是你在使用一个对象。因此List<String>和List<Integer>在运行时事实上都被擦除成原生的List类型。
运行时,使用泛型和使用Object所产生的字节码是相同的。因此,泛型是在编译期对代码进行检查的。
擦除丢失了在泛型代码中执行某些操作的能力。尽管你一厢情愿想地想把泛型参数替换成为你想要的类型,但是实际上它什么都不是。任何在运行时需要知道确切类型信息的操作都将无法工作。
T t = new T(); // Compile error T[] t = new T[](); // Compile error if(c1 instanceof T) {} // Compile error
可以通过引用类型参数来对擦除进行补偿。即显式地传递该类型的class对象。下面介绍了3种方法:
(1) 直接传递一个Class<T>的内建工厂对象。缺点是如果T实例化(newInstance();)失败,编译期不能捕获异常。
1 class ClassAsFactory<T> { 2 T x; 3 public ClassAsFactory(Class<T> kind) { 4 try { 5 x = kind.newInstance(); 6 } catch (Exception e) { 7 throw new RuntimeException(e); 8 } 9 } 10 } 11 12 class Employee { } 13 14 public class InstantiateGenericType { 15 public static void main(String[] args) { 16 new ClassAsFactory<Employee>(Employee.class); 17 System.out.println("ClassAsFactory<Employee> succeeded"); 18 try { 19 // Integer并没有默认构造器,所以调用newInstance()时会报Error 20 new ClassAsFactory<Integer>(Integer.class); 21 } catch (Exception e) { 22 System.out.println("ClassAsFactory<Integer> failed"); 23 } 24 } 25 }
(2) 创建一个显式的工厂对象。它可以很好的限制对象类型。
1 interface FactoryI<T> { 2 T create(); 3 } 4 5 class Foo<T> { 6 public final T x; 7 8 public <F extends FactoryI<T>> Foo(F factory) { 9 x = factory.create(); 10 } 11 } 12 13 // 直接实现FactoryI接口 14 class IntegerFactory implements FactoryI<Integer> { 15 @Override 16 public Integer create() { 17 return new Integer(0); 18 } 19 } 20 21 // 创建静态内部类实现FactoryI接口 22 class Widget { 23 public static class Factory implements FactoryI<Widget> { 24 @Override 25 public Widget create() { 26 return new Widget(); 27 } 28 } 29 } 30 31 public class FactoryConstraint { 32 public static void main(String[] args) { 33 new Foo<Integer>(new IntegerFactory()); 34 new Foo<Widget>(new Widget.Factory()); 35 } 36 }
(3) 使用模版方法设计模式创建抽象类,由具体的子类实现创建对象。
1 abstract class GenericWithCreate<T> { 2 final T element; 3 GenericWithCreate() { 4 element = create(); 5 } 6 abstract T create(); 7 } 8 9 class Clazz { } 10 11 class Creator extends GenericWithCreate<Clazz> { 12 13 Creator() { 14 System.out.println(element.getClass().getSimpleName()); 15 } 16 17 Clazz create() { 18 Clazz clazz = new Clazz(); 19 return clazz; 20 } 21 } 22 23 public class CreatorGeneric { 24 public static void main(String[] args) { 25 new Creator(); // Clazz 26 } 27 }
对于创建泛型数组的情况,一般来说使用ArrayList代替。成功创建泛型数组的唯一方法就是创建一个被擦除类型的新数组(T[] array = (T[]) new Object[size];),并且这个数组的类型只能是Object[]。下面介绍了2种方法:
(1) 使用new Object[size]。缺点是由于擦除,我们无法判定创建好的数组的具体类型。例子中如果改成Integer,则会抛出ClassCastException。
1 public class GenericArray<T> { 2 private T[] array; 3 4 @SuppressWarnings("unchecked") 5 public GenericArray(int size) { 6 array = (T[]) new Object[size]; 7 } 8 9 public void put(int index, T item) { 10 array[index] = item; 11 } 12 13 public T get(int index) { 14 return (T) array[index]; 15 } 16 17 public T[] rep() { 18 return (T[]) array; 19 } 20 21 public static void main(String[] args) { 22 GenericArray<Integer> gai = new GenericArray<Integer>(10); 23 for (int i = 0; i < 10; i++) 24 gai.put(i, i); 25 // Integer[] ia = gai.rep(); // [ERROR] java.lang.ClassCastException 26 Object[] ia = gai.rep(); 27 for (int i = 0; i < ia.length; i++) 28 System.out.print(ia[i] + " "); // 0 1 2 3 4 5 6 7 8 9 29 } 30 }
(2) 使用Array.newInstance(type, size)。这时需要传入一个类型标记,就可以解决(1)中的问题。
1 import java.lang.reflect.Array; 2 3 public class GenericArrayWithTypeToken<T> { 4 private T[] array; 5 6 @SuppressWarnings("unchecked") 7 public GenericArrayWithTypeToken(Class<T> type, int size) { 8 array = (T[]) Array.newInstance(type, size); 9 } 10 11 public void put(int index, T item) { 12 array[index] = item; 13 } 14 15 public T get(int index) { 16 return array[index]; 17 } 18 19 public T[] rep() { 20 return array; 21 } 22 23 public static void main(String[] args) { 24 GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<Integer>(Integer.class, 10); 25 for (int i = 0; i < 10; i++) 26 gai.put(i, i); 27 Integer[] ia = gai.rep(); // It's OK 28 for (int i = 0; i < ia.length; i++) 29 System.out.print(ia[i] + " "); // 0 1 2 3 4 5 6 7 8 9 30 } 31 }