首页 > 技术文章 > Java泛型(5):擦除与补偿

storml 2017-11-30 18:58 原文

先看一个例子:

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 }

 

推荐阅读