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

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 胡志翔 中级黑马   /  2013-9-10 18:03  /  1406 人查看  /  3 人回复  /   1 人收藏 转载请遵从CC协议 禁止商业使用本文

ArrayList<String> al = new ArrayList<String>();
al.getClass().getMethod("add", Object.class).invoke(al, 3);
System.out.println(al.get(0));
上面的代码会报错: java.lang.Integer cannot be cast to java.lang.String
为什么下面的代码正常?
ArrayList<Integer> al = new ArrayList<Integer>();
al.getClass().getMethod("add", Object.class).invoke(al, "abc");
System.out.println(al.get(0));

评分

参与人数 1技术分 +1 收起 理由
EYE_SEE_YOU + 1

查看全部评分

3 个回复

正序浏览
***********
ArrayList<String> al = new ArrayList<String>();
al.getClass().getMethod("add", Object.class).invoke(al, 3);
System.out.println(al.get(0));
***********
1. 我现在做一个实验 就是把你的最后的System.out.println(al.get(0));修改为:al.get(0);
这个时候  是不报错的!!这说明这个报错的原因不在泛型,而是println方法出了问题!!


2. 有一个原则:编译时候 如果有重载方法的调用,编译器要确定运行时候的到底调用哪一个重载方法


你现在ArrayList限定为<String> 这样  编译到最后一行的时候,会直接调用public void println(String x);
调用这个重载的原因是:编译的时候,ArrayList<String>的get方法返回的是String类型的数据
【注意  泛型信息是在编译完成之后被擦出的  编译的过程中是非常有用的】
【如果你不信  你就使用ctrl+左键  点击 println这个位置  就会自动跳到 PrintStream类的public void println(String x) 而不是其他的println重载方法】
所以  编译到最后一行的时候  get方法返回String类型的值,调用的就是println(String x)


3. 现在你的第二行传入的是3,一个基本数据类型的值。JDK自动装箱成Integer类型的变量存放在al的集合中。
因为你使用的是反射  所以 现在要考虑运行时候的运行过程:


al.get(0) 编译时 返回String类型的信息,但是编译完成,反省被擦出,get就返回了Object类型的数据。这样  你这里面al.get(0)运行的得到的结果就相当于这句: (Object)new Integer(3);


现在  你把个强制类型转换的对象(Object)new Integer(3)传给了接收参数是String类型的println方法 , JVM就会去执行这个操作。但是他会发现里面的数据真正的类型是Integer,Integer类型的对象没有办法转换成String类型的对象,因此直接抛出 ClassCastException这个类型转换异常
【注意  之所以可以使用强制类型转换  前提就是 要转换的两个对象之间是具有继承关系的对象  否则  只要你敢转换 就就会抛出类型转换异常】
*****************************回答你第二个问题
ArrayList<Integer> a2 = new ArrayList<Integer>();
a2.getClass().getMethod("add", Object.class).invoke(a2, "12");
System.out.println(al.get(0)); -----运行正常

[1] .   println的重载方法接收的参数有:boolean、整形(int short)、浮点型、char、Object 和 String

[2].  按照刚才的说法 这里编译的时候  a2被限定成元素是Integer的类型。这样编译器在编译到最后一行的时候,就会发现你的get方法返回的是Integer类型,这样编译器就把JVM运行的println方法指定到println(Object obj);【同样  使用ctrl+左键  点击 println这个位置  就会自动跳到 PrintStream类的public void println(Object obj) 而不是其他的println重载方法】

[3]. 现在  你在al中通过反射的形式 传入了字符串"12"  这样  JVM运行时【此时泛型已被擦出】,get方法返回的数据相当于(Object)"12"

  JVM运行到最后一行的时候,就把这个数据(Object)"12"传入到 println(Object obj)方法中   这个参数传递是没问题的。
此时:println(Object obj)的源码如下:


  public void println(Object x) {
        String s = String.valueOf(x);
        synchronized (this) {
            print(s);
            newLine();
        }
    }



这样  相当于  运行了String s = String.valueOf((Object)"12");  这个运行  就是把Object类型的数据 转换成String类型的数据。这个是没有问题的。之后调用print()方法  打印出来就正确了。




所以 问题的根源就在于:编译的时候,根据编译得到的实际参数【有泛型信息在里面】的类型来决定重载方法的调用 ---- 其一
                                       println(String)和println(Object)同样接收数据  一个传入的是Integer 强转到String   报异常   另一个是String转换到Object  没有问题  。 -----其二
这样   你所叙述的情景就出现了。



评分

参与人数 1技术分 +2 收起 理由
EYE_SEE_YOU + 2

查看全部评分

回复 使用道具 举报
这个还真的不懂
不过发现
ArrayList<Integer> al = new ArrayList<Integer>();
al.getClass().getMethod("add", Object.class).invoke(al, "abc");
System.out.println(al.get(0).getClass());
就会报同样的错
回复 使用道具 举报
本帖最后由 杨彬 于 2013-9-10 18:19 编辑
  1.                 ArrayList<String> arr = new ArrayList<String>();
  2.                 Class clazz = arr.getClass();
  3.                 Method method = clazz.getDeclaredMethod("add", Object.class);
  4.                 method.invoke(arr, new Integer(1));
  5.                 method.invoke(arr, new Integer(22));
  6.                 method.invoke(arr, new String("yb"));
  7.                 System.out.println(arr);
复制代码
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马