黑马程序员技术交流社区

标题: 泛型与反射的问题 [打印本页]

作者: mk7    时间: 2013-6-26 10:18
标题: 泛型与反射的问题
本帖最后由 孙百鑫 于 2013-6-30 08:32 编辑

有几行代码很疑惑  请大家分析一下
  1. ArrayList<String> al = new ArrayList<String>();

  2. al.getClass().getMethod("add", Object.class).invoke(al, 3);
  3. System.out.println(al.get(0));
复制代码
上述代码会报错: java.lang.Integer cannot be cast to java.lang.String


而下面的代码却正常 这是为什么??
  1. ArrayList<Integer> al = new ArrayList<Integer>();
  2. al.getClass().getMethod("add", Object.class).invoke(al, "abc");
  3. System.out.println(al.get(0));
复制代码

作者: 曹德君    时间: 2013-6-26 10:41
  1. ArrayList<String> al = new ArrayList<String>();

  2. al.getClass().getMethod("add", Object.class).invoke(al, 3);//集合是存放对象的,3不是对象,是个基本数据类型。
  3. System.out.println(al.get(0));
复制代码

作者: mk7    时间: 2013-6-26 11:01
曹德君 发表于 2013-6-26 10:41

不是有自动装箱与拆箱功能吗

怎么换成al.getClass().getMethod("add", Object.class).invoke(al, new Integer(3)); 也不行

请详细剖析一下 谢谢
作者: 王靖远    时间: 2013-6-26 11:21
这是类型转换错误。
你的泛型定义是String类型,你存入的是Integer类型。
就好像String []s = new String[];
s[0]=1;
作者: 曹德君    时间: 2013-6-26 11:22
mk7 发表于 2013-6-26 11:01
不是有自动装箱与拆箱功能吗

怎么换成al.getClass().getMethod("add", Object.class).invoke(al, new In ...

试了一下,我的解释好像不正确。误导你了。
作者: 曹德君    时间: 2013-6-26 11:28
mk7 发表于 2013-6-26 11:01
不是有自动装箱与拆箱功能吗

怎么换成al.getClass().getMethod("add", Object.class).invoke(al, new In ...

找到实际的原因了,用反射的时候,数字的确装箱存到集合里面了。跟al.get(0)方法有关系,如果直接输出System.out.println(al);是不会报错的。可以看看底层代码,就知道原因了。
作者: mk7    时间: 2013-6-26 12:24
曹德君 发表于 2013-6-26 11:28
找到实际的原因了,用反射的时候,数字的确装箱存到集合里面了。跟al.get(0)方法有关系,如果直接输出Syst ...

底层代码也没看明白 你能给解释一下吗 谢谢
作者: 曹德君    时间: 2013-6-26 13:04
  1. public E get(int index) {
  2.         RangeCheck(index);

  3.         return (E) elementData[index];
  4.     }
复制代码
应该是个泛型方法,在去集合的元素的时候,编译器检测到不属于该类型的就报错了。
作者: mk7    时间: 2013-6-26 15:32
十分感谢   不过我还是没弄太明白  
作者: 张歆明    时间: 2013-6-26 17:35
楼主 您问的问题很好
开始我也是被你的问题困惑了

我现在解释一下
1.最后一个不打印 仅仅是取出  运行时不会报错  我调试了一下 真真正正地看到了  int类型的3被存入collection1这个引用指向的集合对象中
  1. ArrayList<String> collection1 =new ArrayList<String>();
  2.                 ArrayList<Integer> collection2 =new ArrayList<Integer>();

复制代码
调试结果见 图1同时也证明了:get方法也是没问题的 那就应该是println出了问题
此时修改代码最后一行:System.out.println(collection1.get(0));
2. 这个时候 我想看下println的源代码
先是ctrl+f 打开了System.out.println(collection2.get(0)); 处的println()源码
结果是:
  1. public void println(Object x) {
  2.         String s = String.valueOf(x);
  3.         synchronized (this) {
  4.             print(s);
  5.             newLine();
  6.         }
  7.     }
复制代码
由于 编译阶段  已经确定了你这个参数打印是由哪个println来执行的(println的方法有很多重载:八种基本数据类型+ String + Object)
这里面  由于编译的时候 泛型起作用(运行时不起作用)  Integer不属于八种基本数据类型和String  所以 ctrl+f跳转到 public void println(Object x) 这个方法
所以 String s = String.valueOf(x); 会把你的Integer的对象转换成String类型   
毕老师讲过:valueOf可以把任意数据类型转换成String  (实际上这个方法也是重载的) 所以 后面不会报错
下面我说下 为什么会打印出来"abc"
这个时候 我又一次打开了此处valueOf对应的源码:
  1. public static String valueOf(Object obj) {
  2.         return (obj == null) ? "null" : obj.toString();
  3.     }
复制代码
但是 运行时传进来的实际指向String的对象
所以 打印出来的是abc

****************************
但是:
ctrl+f 打开了System.out.println(collection1.get(0));的println的源代码:
  1. public void println(String x) {
  2.         synchronized (this) {
  3.             print(x);
  4.             newLine();
  5.         }
  6.     }
复制代码
这里直接跳转到了参数为String的prrintln的另一个重载方法
可以看到  由于 collection1在编译的时候 实例化参数是String 此时Javac就将这个打印这个集合中元素的方法定位到println(String x)
这个时候  你传进来的是Integer的对象(3) 赋值给参数String x
Intege y =new Integer(3);
也就相当于  String x = y; 所以 运行时会报错!

***********************************************************
这时候编译不报错的原因就是张孝祥老师讲的 泛型在运行的时候不起作用
以下面的为例:javac仅仅按照代码行来检查  如果这一行有多次成员调用  只要语法规则不错就行  不会考虑行与行之间的关系
collection2.getClass().getMethod("add", Object.class).invoke(collection2, "abc");
这里面 javac首先检查 collection2是一个对象 所以 一定有相应的class对象存在  所以collection2.getClass()这段语法正确
getClass()返回Class对象  所以可以调用getMethod()方法  折页编译通过  此时 又检查参数是不是和getMethod声明的样子一不一样(还是语法检查)
public Method getMethod(String, Class<T>...)  这个是getMethod()的声明
实际调用 是getMethod("add", Object.class)  javac一看 "add"是String类型的  Object.class是Class对象  所以 这也通过
最后 invoke(collection2, "abc")传入得实参也是符合invoke的API声明的  所以 javac就认为  这行代码语法正确
这就导致了 整个编译的正确  但是 运行之后取出来的时候  表面上是String类型的对象 但是 实际上是Integer  一传给println(String x)的
参数 x JVM就没办法接受这个骗局了 无法运行  所以 运行时抛出了 异常 但是  javac编译正确的现象

Debug Result.jpg (31.78 KB, 下载次数: 1)

图1 调试结果

图1 调试结果

作者: mk7    时间: 2013-6-26 22:11
张歆明 发表于 2013-6-26 17:35
楼主 您问的问题很好
开始我也是被你的问题困惑了

太给力了 谢谢啊
作者: 张歆明    时间: 2013-6-26 22:14
mk7 发表于 2013-6-26 22:11
太给力了 谢谢啊

好 能帮到你很高兴 我也学习了 不客气 多多交流:)
作者: 孙百鑫    时间: 2013-6-30 08:22
楼主您好,帖子长时间没有动态我已经将您的帖子改成已解决。如有问题请私密我哦~
作者: 张洪慊    时间: 2013-7-23 19:34
张歆明 发表于 2013-6-26 17:35
楼主 您问的问题很好
开始我也是被你的问题困惑了

刚遇到,看见了你的回答,佩服佩服
作者: 张歆明    时间: 2013-7-23 19:38
张洪慊 发表于 2013-7-23 19:34
刚遇到,看见了你的回答,佩服佩服

过奖了 我也是研究了半天 呵呵  常交流 呵呵
作者: 张洪慊    时间: 2013-7-23 19:38
张歆明 发表于 2013-7-23 19:38
过奖了 我也是研究了半天 呵呵  常交流 呵呵

得,QQ拿来
作者: 张洪慊    时间: 2013-7-23 19:39
我的是 245950700:Excalibur




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2