Java 泛型总结(二):泛型与数组
简介
上一篇文章介绍了泛型的基本用法以及类型擦除的问题,现在来看看泛型和数组的关系。数组相比于Java类库中的容器类是比较特殊的,主要体现在三个方面:
- 数组创建后大小便固定,但效率更高
- 数组能追踪它内部保存的元素的具体类型,插入的元素类型会在编译期得到检查
- 数组可以持有原始类型(int,float等),不过有了自动装箱,容器类看上去也能持有原始类型了
那么当数组遇到泛型会怎样?能否创建泛型数组呢?这是这篇文章的主要内容。
这个系列的另外两篇文章:
- Java泛型总结(一):基本用法与类型擦除
- Java泛型总结(三):通配符的使用
泛型数组
如何创建泛型数组
如果有一个类如下:
classGeneric{ }
如果要创建一个泛型数组,应该是这样:Generic
那么如果要使用泛型数组怎么办?一种方案是使用ArrayList,比如下面的例子:
publicclassListOfGenerics{ privateList array=newArrayList (); publicvoidadd(Titem){array.add(item);} publicTget(intindex){returnarray.get(index);} }
如何创建真正的泛型数组呢?我们不能直接创建,但可以定义泛型数组的引用。比如:
publicclassArrayOfGenericReference{ staticGeneric[]gia; }
gia是一个指向泛型数组的引用,这段代码可以通过编译。但是,我们并不能创建这个确切类型的数组,也就是不能使用newGeneric
publicclassArrayOfGeneric{ staticfinalintSIZE=100; staticGeneric[]gia; @SuppressWarnings("unchecked") publicstaticvoidmain(String[]args){ //Compiles;producesClassCastException: //!gia=(Generic [])newObject[SIZE]; //Runtimetypeistheraw(erased)type: gia=(Generic [])newGeneric[SIZE]; System.out.println(gia.getClass().getSimpleName()); gia[0]=newGeneric (); //!gia[1]=newObject();//Compile-timeerror //Discoverstypemismatchatcompiletime: //!gia[2]=newGeneric (); Generic g=gia[0]; } }/*输出: Generic[] *///:~
数组能追踪元素的实际类型,这个类型是在数组创建的时候建立的。上面被注释掉的一行代码:gia=(Generic
我个人的理解是:由于类型擦除,所以Generic
使用T[]array
上面的例子中,元素的类型是泛型类。下面看一个元素本身类型是泛型参数的例子:
publicclassGenericArray{ privateT[]array; @SuppressWarnings("unchecked") publicGenericArray(intsz){ array=(T[])newObject[sz];//创建泛型数组 } publicvoidput(intindex,Titem){ array[index]=item; } publicTget(intindex){returnarray[index];} //Methodthatexposestheunderlyingrepresentation: publicT[]rep(){returnarray;}//返回数组会报错 publicstaticvoidmain(String[]args){ GenericArray gai= newGenericArray (10); //ThiscausesaClassCastException: //!Integer[]ia=gai.rep(); //ThisisOK: Object[]oa=gai.rep(); } }
在上面的代码中,泛型数组的创建是创建一个Object数组,然后转型为T[]。但数组实际的类型还是Object[]。在调用rep()方法的时候,就报ClassCastException异常了,因为Object[]无法转型为Integer[]。
那创建泛型数组的代码array=(T[])newObject[sz]为什么不会报错呢?我的理解和前面介绍的类似,由于类型擦除,相当于转型为Object[],看上去就是没转,但是多了编译器的参数检查和自动转型。而如果把泛型参数改成
publicclassGenericArray{ privateT[]array; @SuppressWarnings("unchecked") publicGenericArray(intsz){ array=(T[])newObject[sz];//创建泛型数组 } publicvoidput(intindex,Titem){ array[index]=item; } publicTget(intindex){returnarray[index];} //Methodthatexposestheunderlyingrepresentation: publicT[]rep(){returnarray;}//返回数组会报错 publicstaticvoidmain(String[]args){ GenericArray gai= newGenericArray (10); //ThiscausesaClassCastException: //!Integer[]ia=gai.rep(); //ThisisOK: Object[]oa=gai.rep(); } }
相比于原始的版本,上面的代码只修改了第一行,把
Exceptioninthread"main"java.lang.ClassCastException:[Ljava.lang.Object;cannotbecastto[Ljava.lang.Integer; atGenericArray.(GenericArray.java:15)
使用Object[]array
由于擦除,运行期的数组类型只能是Object[],如果我们立即把它转型为T[],那么在编译期就失去了数组的实际类型,编译器也许无法发现潜在的错误。因此,更好的办法是在内部最好使用Object[]数组,在取出元素的时候再转型。看下面的例子:
publicclassGenericArray2{ privateObject[]array; publicGenericArray2(intsz){ array=newObject[sz]; } publicvoidput(intindex,Titem){ array[index]=item; } @SuppressWarnings("unchecked") publicTget(intindex){return(T)array[index];} @SuppressWarnings("unchecked") publicT[]rep(){ return(T[])array;//Warning:uncheckedcast } publicstaticvoidmain(String[]args){ GenericArray2 gai= newGenericArray2 (10); for(inti=0;i<10;i++) gai.put(i,i); for(inti=0;i<10;i++) System.out.print(gai.get(i)+""); System.out.println(); try{ Integer[]ia=gai.rep(); }catch(Exceptione){System.out.println(e);} } }/*Output:(Sample) 0123456789 java.lang.ClassCastException:[Ljava.lang.Object;cannotbecastto[Ljava.lang.Integer; *///:~
现在内部数组的呈现不是T[]而是Object[],当get()被调用的时候数组的元素被转型为T,这正是元素的实际类型。不过调用rep()还是会报错,因为数组的实际类型依然是Object[],终究不能转换为其它类型。使用Object[]代替T[]的好处是让我们不会忘记数组运行期的实际类型,以至于不小心引入错误。
使用类型标识
其实使用Class对象作为类型标识是更好的设计:
publicclassGenericArrayWithTypeToken{ privateT[]array; @SuppressWarnings("unchecked") publicGenericArrayWithTypeToken(Class type,intsz){ array=(T[])Array.newInstance(type,sz); } publicvoidput(intindex,Titem){ array[index]=item; } publicTget(intindex){returnarray[index];} //Exposetheunderlyingrepresentation: publicT[]rep(){returnarray;} publicstaticvoidmain(String[]args){ GenericArrayWithTypeToken gai= newGenericArrayWithTypeToken ( Integer.class,10); //Thisnowworks: Integer[]ia=gai.rep(); } }
在构造器中传入了Class
总结
数组与泛型的关系还是有点复杂的,Java中不允许直接创建泛型数组。本文分析了其中原因并且总结了一些创建泛型数组的方式。其中有部分个人的理解,如果错误希望大家指正。下一篇会总结通配符的使用。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持毛票票!