[改善Java代码]数组的真实类型必须是泛型类型的子类型
List接口的toArray方法可以把一个结合转化为数组,但是使用不方便,toArray()方法返回的是一个Object数组,所以需要自行转变.
toArray(T[] a)虽然返回的是T类型的数组,但是还是需要传入一个T类型的数组,这也挺麻烦的.我们期望输入的是一个泛型化的list,这样就能转化为泛型数组了.
看代码:
import java.util.Arrays;
import java.util.List; public class Client<T> {
public static <T> T[] toArray(List<T> list){
T[] t = (T[])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(String[] args) {
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 cn.summerchill.test.Client.main(Client.java:17)
类型转换异常,也就是说,不能把一个Object数组转换为String数组,这段异常包含了两个问题:
1.为什么Object数组不能向下转型为String数组?
数组是一个容器,只有确保容器内的所有元素类型与期望的类型有父子关系时才能转换,Object数组只能保证数组内的元素是Object类型.却不能确保它们都是String的父类型或子类,所以转换失败.
2.为什么是main方法抛出异常,而不是toArray方法?
其实,是在toArray方法中进行的类型向下转换,而不是main方法中,那为什么异常会在main方法中抛出,应该在toArrya方法的 T[] t = (T[])new Object[list.size()] 这段才对啊....
那是因为泛型是类型擦除的,toArray方法经过编译后与如下代码相同:
import java.util.Arrays;
import java.util.List; public class Client<T> {
public static Object[] toArray(List list){
//此处的强制类型没必要存在,只是为了保持与源代码对比
Object[] t = (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(String[] args) {
List<String> list = Arrays.asList("A","B");
for(String str:(String[])toArray(list)){
System.out.println(str);
}
}
}
阅读完此段代码就很清楚了,toArray方法返回后会进行一次类型转换,Object数组转换成了String数组,于是就报了ClassCastException异常了.
Object数组不能转换成String数组,T类型又无法在运行期获得,那该如何解决这个问题呢?
其实要想把一个Object数组转换成为String数组,只要Object数组的实际类型也是String就可以了.
public class Client<T> {
public static void main(String[] args) {
//objArray的实际类型和表面类型都是String数组
Object[] objArray = {"A","B"};
//抛出ClassCastException
String[] strArray = (String[])objArray; String[] ss = {"A","B"};
//objs的真实类型是String数组,显示类型为Object数组
Object[] objs = ss;
//顺利转换为String数组
String[] strs = (String[])objs;
}
}
知道了上面,把泛型数组声明为泛型类的子类型
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.List; public class Client<T> { public static <T> T[] toArray(List<T> list, Class<T> tClass) {
//声明并初始化一个T类型的数组
T[] t = (T[]) Array.newInstance(tClass, list.size());
for(int i=0,n=list.size();i<n;i++){
t[i] = list.get(i);
}
return t;
}
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B");
for (String str : toArray(list,String.class)) {
System.out.println(str);
}
}
}
通过反射类Array声明了一个T类型的数组,由于我们无法在运行期获得泛型类型的参数,因此就需要调用者主动传入T参数类型.此时,客户端再调用就不会出现任何异常了.
在这里我们看到,一个泛型类(特别是泛型集合)转变为泛型数组时,泛型数组的真实类型不能是泛型类型的父类型(比如顶层类Object),只能是泛型类型的子类型(当然包含自身类型),否则就会出现类型转换异常.
[改善Java代码]数组的真实类型必须是泛型类型的子类型的更多相关文章
- 编写高质量代码:改善Java程序的151个建议(第二章:基本类型)
编写高质量代码:改善Java程序的151个建议(第二章:基本类型) 目录 建议21:用偶判断,不用奇判断 建议22:用整数类型处理货币 建议23:不要让类型默默转换 建议24:边界还是边界 建议25: ...
- [改善Java代码]警惕泛型是不能协变和逆变的
什么叫做协变(covariance)和逆变(contravariance)? 在变成语言的类型框架中,协变和逆变是指宽类型和窄类型在某种情况下(如参数,泛型,返回值)替换或交换的特性,简单的说,协变是 ...
- [改善Java代码]避开基本类型数组转换列表陷阱
开发中经常用到Arrays和Collections这两个工具类. 在数组和列表之间进行切换.非常方便.但是也会遇到一些问题. 看代码: import java.util.Arrays; import ...
- [改善Java代码]动态加载不适合数组
上一个建议解释了为什么要使用forName,本建议就说说哪些地方不适合使用动态加载. 如果forName要加载一个类,那它必须是一个类------8中基本类型就排除在外.它们不是一个具体的类. 其次它 ...
- [改善Java代码]不能初始化泛型参数和数组
泛型类型在编译期被擦除,我们在类初始化时将无法获得泛型的具体参数,比如这样的代码: class Foo<T>{ //private T t =new T();//报错Cannot inst ...
- [改善Java代码]用枚举实现工厂方法模式更简洁
工厂方法模式(Factory Method Patter)是"创建对象的接口",让子类决定实例化哪一个类,并使一个类的实例化延迟到其子类.工厂方法模式在我们的开发工作中,经常会用到 ...
- [改善Java代码]小心switch带来的空值异常
使用枚举定义常量时,会伴有大量的switch语句判断,目的是伪类每个枚举项解释其行为,例如: public class Client { public static void main(String[ ...
- [改善Java代码]不同的场景使用不同的泛型通配符
Java泛型支持通配符(Wildcard),可以单独使用一个"?"表示任意类,也可以使用extends关键字标识某一类(接口)的子类型,还可以使用super关键字标识某一类(接口) ...
- [改善Java代码]不要在构造函数中抛出异常
Java的异常机制有三种: 一.Error类以及其子类表示的是错误,它是不需要程序员处理也不能处理的异常.比如VirtualMachineError虚拟机错误,ThreadDeath线程僵尸等. 二. ...
随机推荐
- Struts2 文件上传
一:表单准备 ① 要想使用HTML 表单上传一个或多个文件 –须把 HTML表单的 enctype属性设置为multipart/form-data –须把HTML 表单的method ...
- thymeleaf中的模板布局
一.包括模板片段: 1:定义和引用片段,我们经常会想要包含在模板片段来自其他模板.常见的用途是页脚.标题.菜单…; 为了做到这一点,Thymeleaf需要我们定义包含可用的片段,我们可以通过使用th: ...
- hdu 5640 King's Cake(BestCoder Round #75)
King's Cake Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total ...
- [C语言 - 3] 字符串
字符数组 char * 看做一个特殊的字符数组, 在字符串结束为止添加'\0'结束符 (ASCII码0), 没有\0结尾的是普通的字符数组. 使用双引号定义的字符串自动在尾部加上\0 puts(s)函 ...
- UVaLive 7359 Sum Kind Of Problem (数学,水题)
题意:给定一个n,求前 n 个正整数,正奇数,正偶数之和. 析:没什么好说的,用前 n 项和公式即可. 代码如下: #pragma comment(linker, "/STACK:10240 ...
- struts2文件下载 出现Can not find a java.io.InputStream with the name的错误
成功代码: 前台界面jsp: <a style="text-decoration:none;" href="<%=path %>/main/frontN ...
- ArcObjects SDK(AE)10.1在vs2012安装的方法
ArcObjects SDK(以下简称AO)10.1只支持vs2010,如果装了vs2012,再安装AO会提示一串鸡肠(英文),意思是AO10.1只支持vs2010 想在2012下安装,可以通过修改注 ...
- Intel大坑之中的一个:丢失的SSE2 128bit/64bit 位移指令,马航MH370??
缘由 近期在写一些字符串函数的优化,兴趣使然.但是写的过程中,想要实现 SSE2 128 bit / 64 bit 的按 bit 逻辑位移.遇到了一个大坑,且听我娓娓道来. 我并不想用什么马航370来 ...
- [WebGL] Setting Up WebGL
In this lesson we cover setting up WebGL for use, including creating a canvas, getting the WebGL ren ...
- JS原型连
[推荐]关于JS中的constructor与prototype[转] 最初对js中 object.constructor 的认识: 在学习JS的面向对象过程中,一直对constructor与proto ...