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

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 黑马__李龙 中级黑马   /  2012-8-17 19:15  /  3489 人查看  /  22 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 李龙276596456 于 2012-8-17 19:17 编辑

刚才在写日记,不得不反复看视频,其中有个小的疑问:
在2010年Java高新技术中的37_黑马程序员_张孝祥_Java基础加强_泛型的内部原理及更深应用.avi
里有一段代码是:
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  List<Integer> list = new ArrayList<Integer>();
  list.getClass().getMethod("add", Object.class).invoke(list, "abc");
  System.out.println(list.get(0));
}
运行结果是:abc
结果和视频一样,开始我没注意,但是再看的时候,突然想做以下实验:
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  List<Integer> list = new ArrayList<Integer>();
  list.getClass().getMethod("add", Object.class).invoke(list, "abc");
  System.out.println(list.get(0).getClass());
}
发现出错:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at cn.leo.Game.main(Game.java:31)
接着实验:
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  List<Integer> list = new ArrayList<Integer>();
  list.getClass().getMethod("add", Object.class).invoke(list, "abc");
  System.out.println(list.get(0).toString());
}
仍然出错:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at cn.leo.Game.main(Game.java:31)
不太明白原因,查阅API:
E get(int index) 返回此列表中指定位置上的元素。
跟没说一样,没什么有用的。
查阅源码:
public E get(int index) {
RangeCheck(index);  //检验索引index越界问题,和这个问题无关
return (E) elementData[index];
    }
找到了上面异常的原因,因为(E) elementData[index];有一次强转。
但是第一次,也就是视频中的那个例子是怎么出来的?
请求各位高手能够给予详细的解答

评分

参与人数 1技术分 +2 收起 理由
张立江 + 2 这个问题问的不错!

查看全部评分

22 个回复

倒序浏览
哥们,反射有个概念叫。类型擦除的,也就是
List<Integer> list = new ArrayList<Integer>();
  list.getClass().getMethod("add", Object.class).invoke(list, "abc");

中,一旦使用了list.getClass()反射后,得到的就是ArrayList对应的字节码,没有泛型了。之后得到的method当然可以任意的Object 对象了。

而之后的两个例子中,
list.get(0),这是泛型威力发挥出来了,编译器会发现这应该出来是个Integer对象。接着在运行时,jvm发现出来的根据不是Integer而是String,JVM没法直接把这个String转成Iteger。

评分

参与人数 1技术分 +2 收起 理由
张立江 + 2 很给力!

查看全部评分

回复 使用道具 举报
陈淑飞 发表于 2012-8-17 19:27
哥们,反射有个概念叫。类型擦除的,也就是
中,一旦使用了list.getClass()反射后,得到的就是ArrayList对 ...

还是不明白,能再详细解释下第一段程序为啥能出结果,而后面两个会出错
回复 使用道具 举报
刚又做了个实验:
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<String> list = new ArrayList<String>();
                list.getClass().getMethod("add", Object.class).invoke(list, 123);
                System.out.println(list.get(0));
        }

抛出异常:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
        at cn.leo.Game.main(Game.java:31)
回复 使用道具 举报
List<String> list = new ArrayList<String>();
                 list.getClass().getMethod("add", Object.class).invoke(list, 123);

程序运行这两语是没有问题的,因为是使用反射方法调用add方法,此时调用进来的方法中并不存在泛型类型了(泛型类型在编译后,进行了类型擦除。而反射调用方法是从编译器编译后的Calss文件,加载调用进来生成Class对象,使用此Class 对象获取的add方法,本身就没有限制类型了,只要是Object就可以)。这点与直接调用 add有区别。
因为不是通过反射调用add时,在编译阶段,编译器就能检查出来,add进来的不是Integer,根本就不会让你编译通过。

而运行第三条语句, list.get(0),此是编译器知道你是要返回一个Integer,因为前面定义了泛型。而实际运行期间,在调用ArrayList的get方法时,由于此时正常的调用get方法,泛型发挥作为,JVM会将get出来的Object结果,强转成Integer,但实际0号位置是String对象,那么就没法把String对象强转成Integer了。

所以只要你程序只出来一个get(0),或者remov(0)时,都是有问题的。因为会在方法返回时,会把返回的Object强转成Integer,实际内容又是String。
但是用无返回值的方法如size(),clear(),或add时都没有问题的,其它调用get或remove时也没有问题,只要在确认调用ger或remove的不是0元素就可以了。

关于反射的泛型类型擦除,要是还不怎么懂的话,百度吧。我的解释也没有大师专业呢。共同学习。
回复 使用道具 举报
陈淑飞 发表于 2012-8-17 20:18
程序运行这两语是没有问题的,因为是使用反射方法调用add方法,此时调用进来的方法中并不存在泛型类型了( ...

可是为什么当定义泛型为public static void main(String[] args) throws Exception {
                ArrayList<String> list = new ArrayList<String>();
               
                   list.getClass().getMethod("add", Object.class).invoke(list, 123);
                   System.out.println(list.get(0));
                 }时 ,不能得到123呢。按照类型擦除原理,123是应该能存进去,并得到的啊
回复 使用道具 举报
陈淑飞 发表于 2012-8-17 20:18
程序运行这两语是没有问题的,因为是使用反射方法调用add方法,此时调用进来的方法中并不存在泛型类型了( ...

我想问的问题仅仅是
但是第一次,也就是视频中的那个例子是怎么出来的?
即视频上的例子为什么会打印出来结果,而其他的都在报错?
回复 使用道具 举报
我的问题不是泛型可以用反射放进去,而是取出来时的处理,
我的问题见上一楼
回复 使用道具 举报
李龙276596456 发表于 2012-8-17 21:03
我想问的问题仅仅是
但是第一次,也就是视频中的那个例子是怎么出来的?
即视频上的例子为什么会打印出来 ...

我刚才也试了,一下,确实很诡异,按理第一个能出来,那么当泛型参数是String,Integer类型的应该也能出来结果呀。真是郁闷了,,,求解啊。
回复 使用道具 举报
http://bbs.itheima.com/thread-13280-1-1.html   大家看下这个帖子就明白了。。。
回复 使用道具 举报
先贴上我做的实验,大家就明白了,原理楼下讲
1.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  List<Integer> list = new ArrayList<Integer>();
  list.getClass().getMethod("add", Object.class).invoke(list, "abc");
  System.out.println(list.get(0));
}
运行结果是:abc
和视频一样,
2.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  List<Integer> list = new ArrayList<Integer>();
  list.getClass().getMethod("add", Object.class).invoke(list, "abc");
  System.out.println(list.get(0).getClass());
}
发现出错:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at cn.leo.Game.main(Game.java:31)
3.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  List<Integer> list = new ArrayList<Integer>();
  list.getClass().getMethod("add", Object.class).invoke(list, "abc");
  System.out.println(list.get(0).toString());
}
仍然出错:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at cn.leo.Game.main(Game.java:31)
4.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<String> list = new ArrayList<String>();
                list.getClass().getMethod("add", Object.class).invoke(list, 123);
                System.out.println(list.get(0));
        }

抛出异常:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
        at cn.leo.Game.main(Game.java:31)
5.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                 List<Integer> list = new ArrayList<Integer>();
                  list.getClass().getMethod("add", Object.class).invoke(list, "abc");
                  list.get(0);
        }
正常
6.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<String> list = new ArrayList<String>();
                list.getClass().getMethod("add", Object.class).invoke(list, 123);
                list.get(0);
        }
正常
7.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                 List<Integer> list = new ArrayList<Integer>();
                  list.getClass().getMethod("add", Object.class).invoke(list, "abc");
                  list.get(0).toString();
        }
异常:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
        at cn.leo.Game.main(Game.java:31)
8.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<String> list = new ArrayList<String>();
                list.getClass().getMethod("add", Object.class).invoke(list, 123);
                list.get(0).toString();
        }
异常:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
        at cn.leo.Game.main(Game.java:31)
回复 使用道具 举报
9.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                  List<Integer> list = new ArrayList<Integer>();
                  list.getClass().getMethod("add", Object.class).invoke(list, "123");
                  System.out.println(list.get(0).getClass());
                }
异常:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
        at cn.leo.Game.main(Game.java:31)
10.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<Object> list = new ArrayList<Object>();
                list.getClass().getMethod("add", Object.class).invoke(list, new Object());
                System.out.println(list.get(0));
        }
正常:
打印:java.lang.Object@1888759
11.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<Integer> list = new ArrayList<Integer>();
                list.getClass().getMethod("add", Object.class).invoke(list, new Object());
                System.out.println(list.get(0));
        }
正常打印出:java.lang.Object@4a5ab2
12.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<Integer> list = new ArrayList<Integer>();
                list.getClass().getMethod("add", Object.class).invoke(list, new Integer(1));
                System.out.println(list.get(0));
        }
正常打印出:1
13.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<Integer> list = new ArrayList<Integer>();
                list.getClass().getMethod("add", Object.class).invoke(list, new Long(1));
                System.out.println(list.get(0));
        }
正常打印出:1
14.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<Integer> list = new ArrayList<Integer>();
                list.getClass().getMethod("add", Object.class).invoke(list, new Long(1));
                System.out.println(list.get(0).getClass());
        }
异常:
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer
        at cn.leo.Game.main(Game.java:31)
15.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<Integer> list = new ArrayList<Integer>();
                list.getClass().getMethod("add", Object.class).invoke(list, new Game());
                System.out.println(list.get(0));
        }
正常打印:cn.leo.Game@6e1408
16.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<Integer> list = new ArrayList<Integer>();
                list.getClass().getMethod("add", Object.class).invoke(list, new Game());
                System.out.println(list.get(0).getClass());
        }
异常:
Exception in thread "main" java.lang.ClassCastException: cn.leo.Game cannot be cast to java.lang.Integer
        at cn.leo.Game.main(Game.java:31)
回复 使用道具 举报
17.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<String> list = new ArrayList<String>();
                list.getClass().getMethod("add", Object.class).invoke(list, new Object());
                System.out.println(list.get(0));
        }
异常:
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
        at cn.leo.Game.main(Game.java:31)
18.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<String> list = new ArrayList<String>();
                list.getClass().getMethod("add", Object.class).invoke(list, new Game());
                System.out.println(list.get(0));
        }
异常:
Exception in thread "main" java.lang.ClassCastException: cn.leo.Game cannot be cast to java.lang.String
        at cn.leo.Game.main(Game.java:31)
19.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<Game> list = new ArrayList<Game>();
                list.getClass().getMethod("add", Object.class).invoke(list, new Object());
                System.out.println(list.get(0));
        }
正常打印:java.lang.Object@4a5ab2
20.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
                List<Game> list = new ArrayList<Game>();
                list.getClass().getMethod("add", Object.class).invoke(list, new Integer(1));
                System.out.println(list.get(0));
        }
正常打印:1
回复 使用道具 举报
李龙276596456 发表于 2012-8-17 22:03
先贴上我做的实验,大家就明白了,原理楼下讲
1.
public static void main(String[] args) throws IllegalA ...

D呵呵,前面没怎么看情况,问题。

现在研究了下你上面这段代码,再结合相关的资料。
我可以得出泛型,ArrayList<类名A> list,之类的。 能够成功的比如能:类名A  对象a = list.get(0);的,
都是编译器给的蜜糖,其实上面的代码,经过编译器编译时,会自动帮你加上强转代码即:  
类名A  对象a = (类名名)list.get(0);  没有泛型时,强转操作都是编码人员完成的。现有泛型,编译器就能干这个活了。

接下来再来上面的8个示例吧:
示例1 :
1.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
   List<Integer> list = new ArrayList<Integer>();
   list.getClass().getMethod("add", Object.class).invoke(list, "abc");
   System.out.println(list.get(0));
}

泛型定义的是Integer。而我们知道System.out.println方法可以接受Object。那么这里编译器 知道list.get(0)是Integer,那么就不强转了,因为Integer也是一个Object。
2. 第二段代码中
System.out.println(list.get(0).getClass()); 其实编译器给list.get(0)前面强制加上了(Integer)。list.get(0).getClass(); 被编译器编成了 ((Integer)list.get(0)).getClass()。此时必须报错呢。
3.与上面的一样,在list.get(0)前面被插入强转代码。
4.
List<String> list = new ArrayList<String>();
                 list.getClass().getMethod("add", Object.class).invoke(list, 123);
                 System.out.println(list.get(0));
:
其实这里,虽然list.get(0)后,没有调用如toString或getClass()代码,但是ArrayList被泛型String时,调用S.O.P方法,此方法是可以接受String的,所以编译器会默认成用S.O.P(list.get(0))时,是猜你用调用println中的String参数的方法,所以它又帮你加个 ‘蜜糖’(又先把list.get(0)前面加了个强转的(String)了),这个蜜糖就'有毒'了。

5、6. 示例,不解释,看了前面的解释,还不明白的话,自己baidu吧。
7、8 示例:同理 ,编译器发现 list.get(0)后面继承跟着方法调用代码,所以就也加上了个‘带毒的蜜糖给你’
回复 使用道具 举报
李龙276596456 发表于 2012-8-17 22:50
17.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, Illeg ...

哥们,不用再作实验了。这个问题就是 编译器 干的 ‘好事’。
从 编译器给的 ‘蜜糖’ 角度来分析,就可以分析出,哪里会报异常,哪里会正常运行。
回复 使用道具 举报
原理很简单,就是上面说的:
泛型是编译器类型擦除
在编译期间,当需要调用这个泛型类的方法时,编译期间编译器会在编写二进制文件即.class文件时用泛型类自身方法的二进制码,所以在调用时会有类型转换异常,而不需要调用方法时,,执行的时候也只是按最原始(和非JDK1.5泛型的一样),没有类型转换,,该怎么样怎么样,所以没有异常,另外在代码System.out.println(list.get(0));,根据实验发现,它会编译期间做判断,如果是String类型则不做类型转换,如果不是则进行类型转换,它并不是在编译期间调用泛型类的toString的二级制码,个人猜测可能仅仅做了一个要转换的标志,而在运行期间调用实际类自己的toString方法,以上仅为个人猜想,期待大家的交流。
回复 使用道具 举报
本帖最后由 李龙276596456 于 2012-8-17 23:11 编辑
朱云坤 发表于 2012-8-17 21:53
http://bbs.itheima.com/thread-13280-1-1.html   大家看下这个帖子就明白了。。。


感谢你给我的链接,给我启发比较大。
但是帖子的解释却不能解释这个例子
3.
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
  List<Integer> list = new ArrayList<Integer>();
  list.getClass().getMethod("add", Object.class).invoke(list, "abc");
  System.out.println(list.get(0).toString());
}
仍然出错:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at cn.leo.Game.main(Game.java:31)
回复 使用道具 举报
陈淑飞 发表于 2012-8-17 22:51
D呵呵,前面没怎么看情况,问题。

现在研究了下你上面这段代码,再结合相关的资料。

我还是有些不是很明白
1.S.O.P是什么?
2.为什么猜测要用String参数的方法,还要强转呢?
“调用println中的String参数的方法,所以它又帮你加个 ‘蜜糖’(又先把list.get(0)前面加了个强转的(String)了”
回复 使用道具 举报
陈淑飞 发表于 2012-8-17 22:51
D呵呵,前面没怎么看情况,问题。

现在研究了下你上面这段代码,再结合相关的资料。


首先感谢陈淑飞 童鞋交流经验
我对自己的结论又有了不少改进
泛型是编译器类型擦除
在编译期间,当需要调用这个泛型类的方法时,编译期间编译器会在编写二进制文件即.class文件时用泛型类自身方法的二进制码,所以在调用时会有类型转换异常,实际为陈淑飞 童鞋提到的((Integer)list.get(0)),先进行了一次强转
在代码System.out.println(list.get(0));,根据实验发现,它会编译期间做判断,如果是String类型则不做类型转换,如果不是则进行类型转换,它并不是在编译期间调用泛型类的toString的二进制码,个人猜测可能仅仅做了一个要转换的标志,而在运行期间调用实际类自己的toString方法,
实际为陈淑飞 童鞋提到的System.out.println调用的是不同的方法,一个为:println(Object x),另一个为:println(String x)
再次感谢陈淑飞 童鞋
回复 使用道具 举报
李龙276596456 发表于 2012-8-17 23:21
我还是有些不是很明白
1.S.O.P是什么?
2.为什么猜测要用String参数的方法,还要强转呢?

.class文件中System.out.println方法用的参数类型是String,但是实际参数不是,所以就会报异常了,谢谢
回复 使用道具 举报
12下一页
您需要登录后才可以回帖 登录 | 加入黑马