首页 » 编写高质量代码:改善Java程序的151个建议 » 编写高质量代码:改善Java程序的151个建议全文在线阅读

《编写高质量代码:改善Java程序的151个建议》建议100:数组的真实类型必须是泛型类型的子类型

关灯直达底部

List接口的toArray方法可以把一个集合转化为数组,但是使用不方便,toArray()方法返回的是一个Object数组,所以需要自行转变;toArray(Ta)虽然返回的是T类型的数组,但是还需要传入一个T类型的数组,这也挺麻烦的,我们期望输入的是一个泛型化的List,这样就能转化为泛型数组了,来看看能不能实现,代码如下:


public static<T>TtoArray(List<T>list){

Tt=(T)new Object[list.size()];

for(int i=0,n=list.size();i<n;i++){

t[i]=list.get(i);

}

return t;

}


上面把要输出的参数类型定义为Object数组,然后转型为T类型数组,之后遍历List赋值给数组的每个元素,这与ArrayList的toArray方法很类似(注意只是类似),客户端的调用如下:


public static void main(Stringargs){

List<String>list=Arrays.asList("A","B");

for(String str:toArray(list)){

System.out.println(str);

}

}


编译没有任何问题,运行后出现如下异常:


Exception in thread"main"java.lang.ClassCastException:[Ljava.lang.Object;

cannot be cast to[Ljava.lang.String;at Client.main(Client.java:20)


类型转换异常,也就是说不能把一个Object数组转换为String数组,这段异常包含了两个问题:

为什么Object数组不能向下转型为String数组?

数组是一个容器,只有确保容器内的所有元素类型与期望的类型有父子关系时才能转换,Object数组只能保证数组内的元素是Object类型,却不能确保它们都是String的父类型或子类,所以类型转换失败。

为什么是main方法抛出异常,而不是toArray方法?

其实,是在toArray方法中进行的类型向下转换,而不是main方法中。那为什么异常会在main方法中抛出,应该在toArray方法的"Tt=(T)new Object[list.size()]"这段代码才对呀?那是因为泛型是类型擦除的,toArray方法经过编译后与如下代码相同:


public static ObjecttoArray(List list){

//此处的强制类型没必要存在,只是为了与源代码对比

Objectt=(Object)new Object[list.size()];

for(int i=0,n=list.size();i<n;i++){

t[i]=list.get(i);

}

return t;

}

public static void main(Stringargs){

List<String>list=Arrays.asList("A","B");

for(String str:(String)toArray(list)){

System.out.println(str);

}

}


阅读完此段代码就很清楚了:toArray方法返回后会进行一次类型转换,Object数组转换成了String数组,于是就报ClassCastException异常了。

Object数组不能转为String数组,T类型又无法在运行期获得,那该如何解决这个问题呢?其实,要想把一个Obejct数组转换为String数组,只要Object数组的实际类型(Actual Type)也是String就可以了,例如:


//objArray的实际类型和表面类型都是String数组

ObjectobjArray={"A","B"};

//抛出ClassCastException

StringstrArray=(String)objArray;

Stringss={"A","B"};

//objs的真实类型是String数组,显示类型为Object数组

Objectobjs=ss;

//顺利转换为String数组

Stringstrs=(String)objs;


明白了这个问题,我们就把泛型数组声明为泛型类的子类型吧!代码如下:


public static<T>TtoArray(List<T>list, Class<T>tClass){

//声明并初始化一个T类型的数组

Tt=(T)Array.newInstance(tClass, list.size());

for(int i=0,n=list.size();i<n;i++){

t[i]=list.get(i);

}

return t;

}


通过反射类Array声明了一个T类型的数组,由于我们无法在运行期获得泛型类型的参数,因此就需要调用者主动传入T参数类型。此时,客户端再调用就不会出现任何异常了。

在这里我们看到,当一个泛型类(特别是泛型集合)转变为泛型数组时,泛型数组的真实类型不能是泛型类型的父类型(比如顶层类Object),只能是泛型类型的子类型(当然包括自身类型),否则就会出现类型转换异常。