A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

10黑马币
本帖最后由 a6511631 于 2014-8-20 22:41 编辑

为什么图中红线的部分可以 ,下面的就类转换异常呢。求解释,求详细。

补充:
           问题具体描述:泛型写成<Integer>不报异常,写<String>却会报类型转换异常?
  1. //                ArrayList<Integer> list = new ArrayList<Integer>();
  2. //                list.getClass().getMethod("add", Object.class).invoke(list, "1234");
  3. //                System.out.println(list.get(0));
  4.                 ArrayList<String> list2 = new ArrayList<String>();
  5.                 list2.getClass().getMethod("add", Object.class).invoke(list2, 123);
  6.                 System.out.println(list2.get(0));
复制代码




010544j0gi6n6ccsic7wu9.png.thumb.jpg (10.16 KB, 下载次数: 195)

010544j0gi6n6ccsic7wu9.png.thumb.jpg

最佳答案

查看完整内容

这个问题其实不仅涉及到类型擦除,还涉及到方法重载,下面我试着结合这两点以及实际的字节码为证来解释楼主的问题: 为了方便说明,从第二段代码开始说,这段代码会报错: 下面是对上面代码的解释: 1.用带类型参数的方式定义list变量,类型变量的作用是显式地告诉编译器list是用来装String的,那么就可以获得编译期间的类型检查。编译器在必要的时候会 插入类型转换代码,比如String s = list.get(0),编译器就会把这句代码编译 ...

29 个回复

倒序浏览
本帖最后由 justin1258 于 2014-8-21 17:18 编辑

这个问题其实不仅涉及到类型擦除,还涉及到方法重载,下面我试着结合这两点以及实际的字节码为证来解释楼主的问题:
为了方便说明,从第二段代码开始说,这段代码会报错:
  1. ArrayList<String> list2 = new ArrayList<String>();
  2.         list2.getClass().getMethod("add", Object.class).invoke(list2, 123);
  3.         System.out.println(list2.get(0));
复制代码


下面是对上面代码的解释:
1.用带类型参数的方式定义list变量,类型变量的作用是显式地告诉编译器list是用来装String的,那么就可以获得编译期间的类型检查。编译器在必要的时候会 插入类型转换代码,比如String s = list.get(0),编译器就会把这句代码编译成:String i = (String)list.get(0)。可以看到实质上是编译器帮我们做了转换工作,这是泛型最明显的好处。但是当你声明成:ArrayList list = new ArrayList<String>();的时候就不会有这样的好处,因为编译器看到list是不带泛型参数的原始类型ArrayList,他不知道相关的类型信息,这就要我们手动转换了。
2.第二句代码其实就是将 123 添加到list中,这是允许的,有两个原因:
  1.通过反射的方式调用方法并不会受到编译期的类型检查,只会在运行时进行类型检查。
  2.list类型擦出后其实内部装的还是Object类型元素.
但是这句话无论在什么时候都不会报错,因为在编译期和运行时他都合法。
3.第三句是重点,由于编译器知道list.get(int)返回的是String类型(为什么?上面说过因为受泛型参数的影响)。下面就是寻找调用System.out.println重载方法的过程了,编译知道了参数类型为String这点之后就要去找是否有这个“精确匹配的方法”,确实有,(如何确定重载方法这里不讲),那么就会调用System.out.println(String)这个方法。那么这里编译器就要插入类型转换的代码了,就像这样System.out.println((String)list.get(0));这里就是运行时报ClassCastException异常的原因,因为list.get(0)取出来的实际类型是Integer(因为装箱)。
下面是:相关字节码,只要看用红框标注的就行,第51行checkcast就是类型转换指令:


下面的截图是第54行字节码调用的println方法的具体描述,可以确定他调用的是println(string)重载:


靠,写了那么多。


接下来是第一段代码:
  1. ArrayList<Integer> list = new ArrayList<Integer>();
  2.         list.getClass().getMethod("add", Object.class).invoke(list, "1234");
  3.         System.out.println(list.get(0));
复制代码
额,敲了那么多不是白敲的,有了上面的基础,这里很多地方就不用解释了。直接解释第3句:
这里也是要确定调用按个pringtln重载方法,这时因为编译器知道list.get(0)返回的是Integer,那么编译器首先去找有没有精确匹配的方法Println(Integer),很遗憾,木有。。。那么编译器就要找找有没有其他合适的方法了,找啊找,于是找到了一个比较合适的方法:println(Object)。此时编译器就要调用这个方法了,但是这里和上面的不同,由于Integer类型可以向上转型为Object,所以编译器这里并不会插入类型转换代码,直接调用。。所以啊所以,最终就不会报ClassCastException了。下面是相关的字节码,我们看到编译器并没有生成转型代码,而是直接调用println方法:


具体的重载方法描述:



综上所述,原因在于编译器需要根据list.get(int)放回的类型来确定重载方法,并且在必要的时候进行转型。
如果楼主试着把上面第一段代码的最后一句代码改成:
Integer integer = list.get(0);
System.out.println(integer);

那么同样要报错。

点评

机智的哥们,服了  发表于 2014-8-21 19:57
回复 使用道具 举报


这难道跟脸蛋有关系?
回复 使用道具 举报
masai158 发表于 2014-8-20 21:18
这难道跟脸蛋有关系?

注意看别眨眼。我的打印不是直接打印list哟。
回复 使用道具 举报
本帖最后由 masai158 于 2014-8-20 21:37 编辑
a6511631 发表于 2014-8-20 21:26
注意看别眨眼。我的打印不是直接打印list哟。


mark ......好了。当我没说过。待人回答。。
我上面抛异常了
回复 使用道具 举报
本帖最后由 a6511631 于 2014-8-20 21:48 编辑
masai158 发表于 2014-8-20 21:34
mark ......好了。当我没说过。待人回答。。
我上面抛异常了定义Method的时候写"Object.class"参数不报异常,写"String.class"参数却报异常?
哎,不能省事啊,我改一下帖子,把问题描述的更具体好了。
回复 使用道具 举报
个人简单的理解吧。。。看 invoke 函数。他都抛异常。
学异常是什么???有异常要么 抛 要么 catch。。
当人家抛了3个异常。你觉得你该干什么???


点评

关于异常的处理不在我现在提出的问题的讨论范围之类。 所以在主函数声明上throws Exception一笔带过了,这里没有贴出来,我以为大家应该都知道  发表于 2014-8-20 21:55
回复 使用道具 举报
既然是去泛型了。那么请问。。 集合中。有     add(添加字符串的方法)? NoSuchMethodException : 无法找到某一特定方法时,抛出该异常。


回复 使用道具 举报
masai158 发表于 2014-8-20 22:06
既然是去泛型了。那么请问。。 集合中。有     add(添加字符串的方法)? NoSuchMethodException : 无法 ...

被先前的人搞混了,靠,问题描述写错了,已改了描述,这才是问题的愿意,求解答
回复 使用道具 举报
和添加的(al,"abc");abc是String类型有关吗

点评

不知道你的意思是指什么,可以稍微说详细点吗  发表于 2014-8-20 22:43
回复 使用道具 举报
ArrayList<Integer> list = new ArrayList<Integer>();

list.getClass().getMethod("add", Object.class).invoke(list, "1234");
你的1234被"包围了,代表就是字符串,如果把"去掉,就应该可以,你试试

点评

汗,请仔细看问题描述吧。我是想问为什么第一种情况可以,第二种情况却会出现异常  发表于 2014-8-20 22:49
回复 使用道具 举报
HKing 中级黑马 2014-8-20 22:51:03
12#
本帖最后由 HKing 于 2014-8-20 22:56 编辑

String类型数据存放在常量池中,一旦定义就不能被改变!所以
  1. ArrayList<String> al = new ArrayList<String>();
  2. al.getClass().getMethod("add", Object.class).invoke(al, 13)
复制代码
会报错:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
你将代码改成下面这个样子就OK了!
  1. ArrayList<StringBuilder> al = new ArrayList<StringBuilder>();
  2. al.getClass().getMethod("add", Object.class).invoke(al, 13);
复制代码







回复 使用道具 举报
HKing 发表于 2014-8-20 22:51
String类型数据存放在常量池中,一旦定义就不能被改变!所以会报错:
Exception in thread "main" java.lan ...

额,这个问题的代码定义了String类型数据吗?与问题的联系在哪里呢?
第二种情况中,我直接打印System.out.println(list2);它没报错
换成System.out.println(list2。get(0));就会报异常了。
我知道问题是出现String类型上,但是你这个解释不够详细。


回复 使用道具 举报
HKing 中级黑马 2014-8-20 23:28:43
14#
本帖最后由 HKing 于 2014-8-21 01:07 编辑
a6511631 发表于 2014-8-20 23:00
额,这个问题的代码定义了String类型数据吗?与问题的联系在哪里呢?
第二种情况中,我直接打印System.ou ...

我试了下,发现单独调用al.get(0);并不会报错,而一旦使用String类里面的方法时,就会报错。这就像是一个String类对象里面装的是一个Integer对象,这导致它既不能调用String类的方法,也不能调用Integer方法,就是一个四不像。可以通过类型转换将其转换
  1. Object obj = (Object)al.get(0);
  2. Integer i = (Integer)obj;
  3. System.out.println(i);
复制代码
至于里面的详原因就不清楚了。:lol

回复 使用道具 举报
本帖最后由 a6511631 于 2014-8-21 08:42 编辑
HKing 发表于 2014-8-20 23:28
我试了下,发现单独调用al.get(0);并不会报错,而一旦使用String类里面的方法时,就会报错。这就像是一个S ...

我也是没懂这里的原因是什么才发帖求解释的
回复 使用道具 举报
类型不匹配
回复 使用道具 举报
本帖最后由 java_dream 于 2014-8-21 10:54 编辑

     list2.getClass().getMethod("add", Object.class).invoke(list2, 123);通过getMethod方法获取的是方法签名为add,参数类型是Object.class的方法,在调用invoke是传入的是123,123会自动装箱为Integer类型,而Integer类型是Object的子类,所有此处程序没有问题。(如果调用getMethod方法是传入的参数类型是String.class,因为Integer不是String的子类,所以invoke时会发生异常。这可以论证上面的说法)
    ArrayList<String> list2 = new ArrayList<String>();声明了一个变量list2,传入的类型参数是String,所以list2对象的方法的返回值类型是String.class,所以执行list2.get(0)会发生 java.lang.ClassCastException异常。
回复 使用道具 举报
java_dream 发表于 2014-8-21 10:49
list2.getClass().getMethod("add", Object.class).invoke(list2, 123);通过getMethod方法获取的是方 ...

说实话,我被你说晕了,你按照你的逻辑说说为什么第一种情况是可以的呢
回复 使用道具 举报
不好意思,是我欠考虑了
回复 使用道具 举报
①第一个调用了System.out.println(Object o):方法:【源码调用了String.valueOf(x);】所以不会出错。
②第二个调用了System.out.println(String s)方法:运行时,list.get()取出来都是Object,然后强转String,出错。
----------------
---------------
生成的class文件:
  第一个:
   45  invokevirtual java.util.ArrayList.get(int) : java.lang.Object [48]
   48  invokevirtual java.io.PrintStream.println(java.lang.Object) : void [52]
------
   99  invokevirtual java.util.ArrayList.get(int) : java.lang.Object [48]
102  checkcast java.lang.String [64]               
☆   105  invokevirtual java.io.PrintStream.println(java.lang.String) : void [66]

点评

确实如此,不过现在的问题是:为什么?  发表于 2014-8-21 12:16
回复 使用道具 举报
12下一页
您需要登录后才可以回帖 登录 | 加入黑马