Java 泛型总结(二):泛型与数组
2017-09-21 17:40
441 查看
(转载)https://segmentfault.com/a/1190000005179147
上一篇文章介绍了泛型的基本用法以及类型擦除的问题,现在来看看泛型和数组的关系。数组相比于Java 类库中的容器类是比较特殊的,主要体现在三个方面:
数组创建后大小便固定,但效率更高
数组能追踪它内部保存的元素的具体类型,插入的元素类型会在编译期得到检查
数组可以持有原始类型 ( int,float等 ),不过有了自动装箱,容器类看上去也能持有原始类型了
那么当数组遇到泛型会怎样? 能否创建泛型数组呢?这是这篇文章的主要内容。
这个系列的另外两篇文章:
Java 泛型总结(一):基本用法与类型擦除
Java 泛型总结(三):通配符的使用
如果有一个类如下:
如果要创建一个泛型数组,应该是这样:
那么如果要使用泛型数组怎么办?一种方案是使用
如何创建真正的泛型数组呢?我们不能直接创建,但可以定义泛型数组的引用。比如:
数组能追踪元素的实际类型,这个类型是在数组创建的时候建立的。上面被注释掉的一行代码:
我个人的理解是:由于类型擦除,所以
上面的例子中,元素的类型是泛型类。下面看一个元素本身类型是泛型参数的例子:
在上面的代码中,泛型数组的创建是创建一个
那创建泛型数组的代码
相比于原始的版本,上面的代码只修改了第一行,把
由于擦除,运行期的数组类型只能是
现在内部数组的呈现不是
因为数组的实际类型依然是
其实使用
在构造器中传入了
数组与泛型的关系还是有点复杂的,Java 中不允许直接创建泛型数组。本文分析了其中原因并且总结了一些创建泛型数组的方式。其中有部分个人的理解,如果错误希望大家指正。下一篇会总结通配符的使用。
简介
上一篇文章介绍了泛型的基本用法以及类型擦除的问题,现在来看看泛型和数组的关系。数组相比于Java 类库中的容器类是比较特殊的,主要体现在三个方面:数组创建后大小便固定,但效率更高
数组能追踪它内部保存的元素的具体类型,插入的元素类型会在编译期得到检查
数组可以持有原始类型 ( int,float等 ),不过有了自动装箱,容器类看上去也能持有原始类型了
那么当数组遇到泛型会怎样? 能否创建泛型数组呢?这是这篇文章的主要内容。
这个系列的另外两篇文章:
Java 泛型总结(一):基本用法与类型擦除
Java 泛型总结(三):通配符的使用
泛型数组
如何创建泛型数组
如果有一个类如下:class Generic<T> { }
如果要创建一个泛型数组,应该是这样:
Generic<Integer> ga = new Generic<Integer>[]。不过行代码会报错,也就是说不能直接创建泛型数组。
那么如果要使用泛型数组怎么办?一种方案是使用
ArrayList,比如下面的例子:
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>[]。具体参见下面的例子:
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>也不需要我们手动转型。
使用 T[] array
上面的例子中,元素的类型是泛型类。下面看一个元素本身类型是泛型参数的例子: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[] array
由于擦除,运行期的数组类型只能是 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基础加强--04.代码简化 书写规律III_数组参数【重载】【数组】【可变参数数组】【泛型可变参数数组】【个人总结】
- java中泛型创建数组的总结
- [转]java中泛型创建数组的总结
- Java中泛型数组创建总结
- java中泛型创建数组的总结
- Java学习总结8——泛型2(不支持泛型数组)
- Java 泛型总结(二):泛型与数组
- java中泛型创建数组的总结
- java中泛型创建数组的总结
- java 泛型与数组
- 黑马程序员_Java第21天知识总结对象流_管道流_RandomAccessFile_数据流_字节数组流
- paip.数组以及集合的操作uapi java php python总结..
- java 泛型总结
- Java总结篇系列:Java泛型
- java泛型的学习总结
- java基础总结 --- 泛型 擦除、边界、通配符、
- 数据结构与算法分析笔记与总结(java实现)--数组3:调整数组顺序使奇数位于偶数前面
- java和js中遍历数组和对象的总结
- JAVA 泛型对象数组(本来想玩泛型随机流泛型对象数组在加点类集的综合程序)
- Java基础加强总结(二)——泛型