class Message<T>{//类上使用泛型
private T msg;
public void setMsg(T msg){
this.msg=msg;
}
public T getMsg(){
return msg;
}
}
public class Main {
public static void main(String[] args) {
Message m1 = new Message();//泛型擦除
m1.setMsg(1200);//Object msg=1200;
Message<String> temp=m1;//把Message<String>换成Message<Boolean>或者Message<Stringbuffer>等都不会报错
//唯独写成Message<String>运行时就会出现类转换异常
System.out.println(temp.getMsg());
}
}
看了下字节码,发现其他泛型擦除后都是Object
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
唯独写成Message<String>
,泛型擦除后字节码的有一个地方是String类型的。
CHECKCAST java/lang/String
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
这个CHECKCAST
是java虚拟机里面的类型转换检查指令
不过我还是挺疑惑的?写个别的泛型类型都没事,怎么就写个String还蹦出来类型转换检查了???
然后就出现了类转换异常,搞了半天也没看懂????
其实这个疑问涉及到了一些java运行机制的问题
2020.5.17研究(2020.6.6已修改部分错误猜想)
今天仔细的一想,发现只有这句代码出现了异常System.out.println(temp.getMsg());
,如果注释掉该行代码,那么程序就不会报错。
检查到设置的泛型类型是String,所以打印输出时会调用PrintStream类的println(String s)方法,这也就是为什么字节码里面会出现PrintStream.println (Ljava/lang/String;)V
的原因。
2020.6.6(类转换异常的罪魁祸首)
目前为止到了今天,已经学完了io流,PrintStream类是字节打印流,就是把OutputStream类封装在里面,这样就能避免我们在输出时手动的转成字节才能输出。
当我设置了Message<String> temp
时,泛型类型是String。
这里补充一个小知识点。
System.out.println()
的System.out是进行屏幕输出的操作对象,是一个PrintStream类的实例化对象。
后面的println()是PrintStream类的方法。
所以,当我设置泛型是String时,println方法对应着会调用public void println(String x)
而当我设置泛型是其他类型时,println方法对应着会调用public void println(Object x)
(可以看jdk源码分析)
这时候坑就来了,首先,temp.getMsg()得到的是什么?是Object对象(由于泛型擦除Integer向上转型成了Object类型)!
所以 System.out.println(temp.getMsg());
,这个语句就会把temp.getMsg() 传给这个方法public void println(String x)
。
就会导致如下操作,直接把一个Object类型的对象赋值给String类型。
String x=temp.getMsg();
由于这一切都是在运行时发生的,编译器没有办法给我们检测出来,所以就会出现类 转 换 异 常
解答:擅用百度,解其疑惑。
http://bbs.itheima.com/thread-13280-1-1.html
这个帖子完美解答了我的疑问,同时在我后期的研究中发现相吻合。
从我在上文列举的字节码不同之处就可以看到有PrintStream.println (Ljava/lang/String;)V,这已类和方法在这个贴子中也同样出现了,所以我点进去一看,恍然大悟。
帖子内容:
定义泛型为Integer时,内部元素取出后的类型应为Integer类型,在打印时就需要调用toString方法。
而定义泛型为String时,内部元素取出后类型应为String类型,在打印String类型时,并不调用其toSting方法(参见PrintStream类print(String s)方法)。而是按照平台的默认字符编码将字符串的字符转换为字节。
有参数可知,当定义泛型为Integer时,传入的参数类型是String类型,但根据泛型限定,它被规定为Integer类型,可以理解为带着Integer类型面具的String对象,打印时调用对象toString方法,而且这个对象本身有toString方法,所以能被打印。
定义泛型为String时,内部元素取出后应为String类型,在打印时不需要调用toString方法,而是直接打印,但其对象实际为Integer类型,编译器不调用toString方法就没有办法打印,此时就发生了实际类型与泛型限定的取出类型不匹配异常。