上一篇文章介绍了泛型的基本用法以及类型擦除的问题,如今来看看泛型和数组的关系。数组相比于Java 类库中的容器类是比较特殊的,主要体如今三个方面:java
那么当数组遇到泛型会怎样? 可否建立泛型数组呢?这是这篇文章的主要内容。编程
这个系列的另外两篇文章:segmentfault
若是有一个类以下:数组
class Generic<T> { }
若是要建立一个泛型数组,应该是这样: Generic<Integer> ga = new Generic<Integer>[]
。不过行代码会报错,也就是说不能直接建立泛型数组。设计
那么若是要使用泛型数组怎么办?一种方案是使用 ArrayList
,好比下面的例子:code
public class ListOfGenerics<T> { private List<T> array = new ArrayList<T>(); public void add(T item) { array.add(item); } public T get(int index) { return array.get(index); } }
如何建立真正的泛型数组呢?咱们不能直接建立,但能够定义泛型数组的引用。好比:对象
public class ArrayOfGenericReference { static Generic<Integer>[] gia; }
gia
是一个指向泛型数组的引用,这段代码能够经过编译。可是,咱们并不能建立这个确切类型的数组,也就是不能使用 new Generic<Integer>[]
。具体参见下面的例子:get
public class ArrayOfGeneric { static final int SIZE = 100; static Generic<Integer>[] gia; @SuppressWarnings("unchecked") public static void main(String[] args) { // Compiles; produces ClassCastException: //! gia = (Generic<Integer>[])new Object[SIZE]; // Runtime type is the raw (erased) type: gia = (Generic<Integer>[])new Generic[SIZE]; System.out.println(gia.getClass().getSimpleName()); gia[0] = new Generic<Integer>(); //! gia[1] = new Object(); // Compile-time error // Discovers type mismatch at compile time: //! gia[2] = new Generic<Double>(); Generic<Integer> g = gia[0]; } } /*输出: Generic[] *///:~
数组能追踪元素的实际类型,这个类型是在数组建立的时候创建的。上面被注释掉的一行代码: gia = (Generic<Integer>[])new Object[SIZE]
,数组在建立的时候是一个 Object
数组,若是转型便会报错。成功建立泛型数组的惟一方式是建立一个类型擦除的数组,而后转型,如代码: gia = (Generic<Integer>[])new Generic[SIZE]
。gia
的 Class
对象输出的名字是 Generic[]
。编译器
我我的的理解是:因为类型擦除,因此 Generic<Integer>
至关于初始类型 Generic
,那么 gia = (Generic<Integer>[])new Generic[SIZE]
中的转型其实仍是转型为 Generic[]
,看上去像没转,可是多了编译器对参数的检查和自动转型,向数组插入 new Object()
和 new Generic<Double>()
均会报错,而 gia[0]
取出给 Generic<Integer>
也不须要咱们手动转型。it
上面的例子中,元素的类型是泛型类。下面看一个元素自己类型是泛型参数的例子:
public class GenericArray<T> { private T[] array; @SuppressWarnings("unchecked") public GenericArray(int sz) { array = (T[])new Object[sz]; // 建立泛型数组 } public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } // Method that exposes the underlying representation: public T[] rep() { return array; } //返回数组 会报错 public static void main(String[] args) { GenericArray<Integer> gai = new GenericArray<Integer>(10); // This causes a ClassCastException: //! Integer[] ia = gai.rep(); // This is OK: Object[] oa = gai.rep(); } }
在上面的代码中,泛型数组的建立是建立一个 Object
数组,而后转型为 T[]
。但数组实际的类型仍是 Object[]
。在调用 rep()
方法的时候,就报 ClassCastException
异常了,由于 Object[]
没法转型为 Integer[]
。
那建立泛型数组的代码 array = (T[])new Object[sz]
为何不会报错呢?个人理解和前面介绍的相似,因为类型擦除,至关于转型为 Object[]
,看上去就是没转,可是多了编译器的参数检查和自动转型。而若是把泛型参数改为 <T extends Integer>
,那么由于类型是擦除到第一个边界,因此 array = (T[])new Object[sz]
中至关于转型为 Integer[]
,这应该会报错。下面是实验的代码:
public class GenericArray<T extends Integer> { private T[] array; @SuppressWarnings("unchecked") public GenericArray(int sz) { array = (T[])new Object[sz]; // 建立泛型数组 } public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } // Method that exposes the underlying representation: public T[] rep() { return array; } //返回数组 会报错 public static void main(String[] args) { GenericArray<Integer> gai = new GenericArray<Integer>(10); // This causes a ClassCastException: //! Integer[] ia = gai.rep(); // This is OK: Object[] oa = gai.rep(); } }
相比于原始的版本,上面的代码只修改了第一行,把 <T>
改为了 <T extends Integer>
,那么不用调用 rep()
,在建立泛型数组的时候就会报错。下面是运行结果:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; at GenericArray.<init>(GenericArray.java:15)
因为擦除,运行期的数组类型只能是 Object[]
,若是咱们当即把它转型为 T[]
,那么在编译期就失去了数组的实际类型,编译器也许没法发现潜在的错误。所以,更好的办法是在内部最好使用 Object[]
数组,在取出元素的时候再转型。看下面的例子:
public class GenericArray2<T> { private Object[] array; public GenericArray2(int sz) { array = new Object[sz]; } public void put(int index, T item) { array[index] = item; } @SuppressWarnings("unchecked") public T get(int index) { return (T)array[index]; } @SuppressWarnings("unchecked") public T[] rep() { return (T[])array; // Warning: unchecked cast } public static void main(String[] args) { GenericArray2<Integer> gai = new GenericArray2<Integer>(10); for(int i = 0; i < 10; i ++) gai.put(i, i); for(int i = 0; i < 10; i ++) System.out.print(gai.get(i) + " "); System.out.println(); try { Integer[] ia = gai.rep(); } catch(Exception e) { System.out.println(e); } } } /* Output: (Sample) 0 1 2 3 4 5 6 7 8 9 java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; *///:~
如今内部数组的呈现不是 T[]
而是 Object[]
,当 get()
被调用的时候数组的元素被转型为 T
,这正是元素的实际类型。不过调用 rep()
仍是会报错, 由于数组的实际类型依然是Object[]
,终究不能转换为其它类型。使用 Object[]
代替 T[]
的好处是让咱们不会忘记数组运行期的实际类型,以致于不当心引入错误。
其实使用 Class
对象做为类型标识是更好的设计:
public class GenericArrayWithTypeToken<T> { private T[] array; @SuppressWarnings("unchecked") public GenericArrayWithTypeToken(Class<T> type, int sz) { array = (T[])Array.newInstance(type, sz); } public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } // Expose the underlying representation: public T[] rep() { return array; } public static void main(String[] args) { GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<Integer>( Integer.class, 10); // This now works: Integer[] ia = gai.rep(); } }
在构造器中传入了 Class<T>
对象,经过 Array.newInstance(type, sz)
建立一个数组,这个方法会用参数中的 Class
对象做为数组元素的组件类型。这样建立出的数组的元素类型便再也不是 Object
,而是 T
。这个方法返回 Object
对象,须要把它转型为数组。不过其余操做都不须要转型了,包括 rep()
方法,由于数组的实际类型与 T[]
是一致的。这是比较推荐的建立泛型数组的方法。
数组与泛型的关系仍是有点复杂的,Java 中不容许直接建立泛型数组。本文分析了其中缘由而且总结了一些建立泛型数组的方式。其中有部分我的的理解,若是错误但愿你们指正。下一篇会总结通配符的使用,有兴趣的读者可进入下一篇:Java 泛型总结(三):通配符的使用。
参考
若是个人文章对您有帮助,不妨点个赞支持一下(^_^)