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

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 未知数|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打印出来,就不会报错了。  




评分

参与人数 1技术分 +1 收起 理由
狼王 + 1 注意格式

查看全部评分

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马