本帖最后由 未知数|X| 于 2013-10-30 07:04 编辑
原文http://blog.csdn.net/shengfeixiang/article/details/8856465
大家都知道,在集合上使用泛型,可以大大的减少运行时期出现的错误,并且效率还不会降低。有这两个方面的原因:
第一、在定义集合的时候使用泛型,会在编译时期就限定了传入集合中元素的类型,所以保证了传入元素的类型。
第二、运行之前,编译器会将集合上的限定类型擦出,就像没有加入限定类型一样,从而效率不会降低。
可是,大家还知道,反射是会越过编译器,从而加入限定类型以外的其他类型,这是反射强大的一方面。看个例子就知道了:
@Test public void test5() throws Exception {
ArrayList<Integer> list = new ArrayList<Integer>();
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, "abc");
System.out.println(list.get(0)); } 但是,你真的认为编译器会去掉“类型”信息吗?这种擦出真的实现了吗?我表示怀疑,请看下面的例子: 下面有四个测试:
测试1
@Test public void test1() throws Exception {
//限定为Integer类型
List<Integer> list = new ArrayList<Integer>();
System.out.println(list.getClass().getName());
//java.util.ArrayList
list.add(12);
//反射获得add方法,并加入非限定类型元素
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, "aaa");
System.out.println(list.getClass().getName());//java.util.ArrayList
System.out.println(list.get(1));//结果为:aaa
/* System.out.println(list.get(1).getClass().getName()); 报错: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String */
//分段转换
Object ob = list.get(1);
String str = (String)ob;
System.out.println(str);//结果为:aaa
//直接转换--->报错:Cannot cast from Integer to String
//String str2 = (String)list.get(1); }
测试2
@Test public void test2() throws Exception {
//限定为Integer类型
ArrayList<Integer> list = new ArrayList<Integer>();
//反射获取add方法,可以加入任意类型值
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, "bbb");
System.out.println(list.getClass().getName());//java.util.ArrayList
System.out.println(list.get(0));//结果为:bbb /* System.out.println(list.get(0).getClass().getName()); 报错: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String */ //反射获取get方法
Method method1 = list.getClass().getMethod("get", int.class);
String str = (String)method1.invoke(list, 0);
System.out.println(str);//结果为:bbb }
测试3
@Test public void test3() throws Exception {
//限定为String类型
ArrayList<String> list = new ArrayList<String>();
//反射获得add方法,并加入非限定类型数据
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, 123);
/* System.out.println(list.get(0)); 报错: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String */
//分段转换
Object obj = list.get(0);
Integer num = (Integer)obj;
System.out.println(num);
//直接转换--->报错:Cannot cast from String to Integer //Integer i2 = (Integer)list.get(1); }
测试4
@Test public void test4() throws Exception {
//限定为String类型
ArrayList<String> list = new ArrayList<String>();
//反射获得add方法,并加入非String类型的值
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, 123);
//反射获取get方法
Method method2 = list.getClass().getMethod("get", int.class);
Integer num = (Integer)method2.invoke(list, 0); System.out.println(num);//123
System.out.println(method2.invoke(list, 0).getClass().getName());//java.lang.Integer }
一、问题出来了 比较测试1和测试2(注意:限定类型都为Integer)
1、在测试1中,直接打印list.get(1)是可以通过编译的,但是当打印list.get(1).getClass().getName()时就报错了,
2、如果直接强转并赋给String就会出问题,但是“分段转换”后,就能通过;另外通过反射获取get方法,就能直接获得“非法”加入的“bbb”这个字符串。
比较测试1和测试2(注意:测试3限定类型为String)
1、在测试1中,直接打印list.get(0)是可以通过编译的;而测试3中直接打印就出问题了
2、测试1和3的“分段转换”都是可以通过的,但是直接强转赋值,就会无法通过
是不是比较了这几个之后,你也会怀疑了呢?难道真的没有完全擦出吗?
我本以为真的没有完全擦出,但是我有比较了一下,只要是通过反射得到的结果都是可以直接获得的。而list直接调用的只有限定了String类型的集合是不能通过编译的。
其实确实擦出了“类型”信息。但是对于String很特殊。
二、我的想法
我来一个一个分析:
1、测试1中可直接打印 list.get(1),就可以通过,而不能直接得到 list.get(1).getClass().getName()的结果,为什么呢?
我个人认为,集合限定的类型是Integer,get()本身返回的也是Integer;如果单独打印list.get(1),是可以自动转为Object打印的,数据封装到了Object中,就不存在由Integer转换为String的问题了。
但是对于获取其字节码就不同了,因为get()返回的是Integer类型,但是从集合中获取位置1上的是String类型的值,我们可以将list.get(1)看成一个对象,那么要调用它的getClass()方法,这时候,编译器就开始检查了,他只知道get()返回的是Integer,不知道里面装了String的值,但是在运行时候就出了问题,JVM虚拟机,发现了 get()本身返回的是Integer,而获取到的却是你非法传入的String类型,虽然欺骗编译器,但是运行时被发现了,因此就不能调用它的方法了,所以就报错了。
那么,难道就没有办法获取到它的list.get(1)的类型了吗?答案是可以获得
那就再欺骗一次JVM虚拟机呗,让他误认为是Object类型就可以了,其实还是将数据封装到Object中,像下面的一样:
System.out.println("get(1):" + ((Object)list.get(1)).getClass().getName()); 这样就可以获得想要的结果了:get(1):java.lang.String 2、可是分段处理就不一样了,由于确实将类型具体落实到了各自的类型上,那么该是什么类型就是什么类型,因此就不会报错的。
3、为什么反射就能够直接获得想要的结果呢?
其实也是一个道理,我们观察就能发现,invoke()这个方法返回的类型是Object类型的,我们通常情况下都会强转的。我个人认为invoke()其实也自动将任何类型都作为Object返回的,也就是说它屏蔽了方法本身的类型,直接把存入的数据作为Object返回了,所以就能直接强转,赋值给指定传入的数据的类型了。
4、在测试3中,为什么直接打印list.get(0)就不行呢?
我们发现,测试3中集合限定的类型是String,难道String就那么特殊吗?
这个和测试1中的不同,似乎没有封装到Object中,在运行是被发现传入了非法类型值,所以就报了错。这个是String的特殊之处。究其原因,我个人认为是String比较特殊,没有将数据封装到Object中,从而导致了在运行时,get()本身返回的String类型和传入的Integer类型不符,就报了错:java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
这个的解决办法还是欺骗JVM虚拟机:
System.out.println((Object)list.get(0)); 这样就能通过了,其实就是屏蔽get()本身的类型,将数据作为Object打印出来,就不会报错了。
|