黑马程序员技术交流社区

标题: 关于泛型的问题 [打印本页]

作者: 戴振良    时间: 2014-1-5 21:51
标题: 关于泛型的问题
本帖最后由 戴振良 于 2014-1-6 20:57 编辑

    我是一名老黑马程序员了,好久没来论坛了,因为最近换了工作了,最近发觉自己好多基础东西都不会,回过头来学Java基础了,看了Java基础加强,遇到了个问题,于是想到黑马来问问大家。

    在张孝详老师的高新技术教程中,视频“37_泛型的内部原理及更深应用.avi”中说到了,当class转换成字节码存入内在时会去掉泛型信息,因此通过反射可以跳过泛型的约束,如下例子的ArrayList限定了只能存入Integer类型的对象,但是通过反射存入了字符串“abc”:
ArrayList<Integer> list =new ArrayList<Integer>();
Method addMethod = list.getClass().getMethod("add", Object.class);
addMethod.invoke(list, "abc");
System.out.println(list.get(0));
运行通过,并输出abc
把上面的Integer替换为String,把"abc"替换为1,其他不变如下:
ArrayList<String> list = newArrayList<String>();
Method addMethod =list.getClass().getMethod("add", Object.class);
addMethod.invoke(list, 1);
System.out.println(list.get(0));
编译通过,运行报错,如下:
Exception in thread"main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
   atcom.dzl.demo.Main.main(Main.java:19)
问题:这里的错误很明显是说运行时是知道了这个list的泛型信息,即知道了list只能存String,不然为什么会说Integer不能转换为String呢?如果真是这样,为什么第一个例子又不报String不能转换为Integer的异常呢?
哎,要是张老师还在就可以直接问问他了,好怀念张老师啊。
   补充,我所用的jdk版本是1.6


作者: 程玉习    时间: 2014-1-5 22:20
用反射的方式可以向Integer类型中加个String类型,反过来就不行,add方法加的实际类型必须是引用类型,不知道对不对,还望指教
作者: 范二青年    时间: 2014-1-5 22:25
可以参考这个博客http://bbs.csdn.net/topics/390302204
别看前面给分的  都是扯淡 没用的
看一下最后一条就明白了

public static void main(java.lang.String[])   throws java.lang.Exception;
  Code:
   0:   new     #2; //class java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   aload_1
   9:   iconst_1
   10:  invokestatic    #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Int
eger;
   13:  invokevirtual   #5; //Method java/util/ArrayList.add:(Ljava/lang/Object;
)Z
   16:  pop
   17:  getstatic       #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   20:  aload_1
   21:  iconst_0
   22:  invokevirtual   #7; //Method java/util/ArrayList.get:(I)Ljava/lang/Objec
t;
   25:  invokevirtual   #8; //Method java/io/PrintStream.println:(Ljava/lang/Obj
ect;)V
   28:  new     #2; //class java/util/ArrayList
   31:  dup
   32:  invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   35:  astore_2
   36:  aload_2
   37:  ldc     #9; //String abc
   39:  invokevirtual   #5; //Method java/util/ArrayList.add:(Ljava/lang/Object;
)Z
   42:  pop
   43:  getstatic       #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   46:  aload_2
   47:  iconst_0
   48:  invokevirtual   #7; //Method java/util/ArrayList.get:(I)Ljava/lang/Objec
t;
   51:  invokevirtual   #8; //Method java/io/PrintStream.println:(Ljava/lang/Obj
ect;)V
   54:  new     #2; //class java/util/ArrayList
   57:  dup
   58:  invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   61:  astore_3
   62:  aload_3
   63:  new     #10; //class Test3
   66:  dup
   67:  invokespecial   #11; //Method Test3."<init>":()V
   70:  invokevirtual   #5; //Method java/util/ArrayList.add:(Ljava/lang/Object;
)Z
   73:  pop
   74:  getstatic       #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   77:  aload_3
   78:  iconst_0
   79:  invokevirtual   #7; //Method java/util/ArrayList.get:(I)Ljava/lang/Objec
t;
   82:  invokevirtual   #8; //Method java/io/PrintStream.println:(Ljava/lang/Obj
ect;)V
   85:  new     #2; //class java/util/ArrayList
   88:  dup
   89:  invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   92:  astore  4
   94:  aload   4
   96:  ldc     #9; //String abc
   98:  invokevirtual   #5; //Method java/util/ArrayList.add:(Ljava/lang/Object;
)Z
   101: pop
   102: getstatic       #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   105: aload   4
   107: iconst_0
   108: invokevirtual   #7; //Method java/util/ArrayList.get:(I)Ljava/lang/Objec
t;
   111: invokevirtual   #8; //Method java/io/PrintStream.println:(Ljava/lang/Obj
ect;)V
   114: new     #2; //class java/util/ArrayList
   117: dup
   118: invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   121: astore  5
   123: aload   5
   125: ldc     #9; //String abc
   127: invokevirtual   #5; //Method java/util/ArrayList.add:(Ljava/lang/Object;
)Z
   130: pop
   131: getstatic       #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   134: aload   5
   136: iconst_0
   137: invokevirtual   #7; //Method java/util/ArrayList.get:(I)Ljava/lang/Objec
t;
   140: checkcast       #12; //class java/lang/String  
   143: invokevirtual   #13; //Method java/io/PrintStream.println:(Ljava/lang/St
ring;)V
   146: return
}
java在处理几种类型的时候处理方式不一样,具体细节我也不是特别清楚,你可以看一下

作者: 李兴    时间: 2014-1-5 22:27
请看这个帖子
http://bbs.itheima.com/forum.php ... mp;page=1#pid605046
作者: 戴振良    时间: 2014-1-5 23:34
本帖最后由 戴振良 于 2014-1-5 23:50 编辑
范二青年 发表于 2014-1-5 22:25
可以参考这个博客http://bbs.csdn.net/topics/390302204
别看前面给分的  都是扯淡 没用的
看一下最后一条 ...

  高手啊,把字节码都搞出来了,不过我还没有那么高的水平,看不懂字节码,不过还是很感谢你,本想给你加个技术分,可是我没有这个权限,给不了,以前是可以给金币的,现在金币也给不了了,不知道为什么,所以只能谢谢你了!
       再来说说一些人的误解:
       说println()方法不可以打印Integer的是错误的,如下代码运行通过:
       System.out.println(new Integer(5));      

      我再举个例子来证明异常的出现与println()方法是没有关系的,是根对象的方法有关系,因为我们知道打印一个对象实际上是调用了一个对象的toString方法,如下:
  1. ArrayList<String> list = new ArrayList<String>();
  2.                 Method addMethod = list.getClass().getMethod("add", Object.class);
  3.                 addMethod.invoke(list, 1);
  4.                 list.get(0);
复制代码

如上代码运行通过,这样我们可以知道没有调用list.get(0) 对象的方法时,该对象不会进行类型转换。把最后一句改成如下:

  1. ArrayList<String> list = new ArrayList<String>();
  2. Method addMethod = list.getClass().getMethod("add", Object.class);
  3. addMethod.invoke(list, 1);
  4. String str = list.get(0).toString();
复制代码
这里没有打印语句,报的异常和上面的是一样一样的。
我把上面的最后一句代码改成如下,运行通过:
  1. String str = ((Object) list.get(0)).    toString();
复制代码
这里我使用了排除法,锁定了问题所在,问题就在直接调用list.get(0) 对象的方法时,Java内部会把list.get(0)先转换成String对象,结果转不了,所以报错了,而我后面用了强转,把list.get(0)  强转为Object,这时系统就不会把 list.get(0) 转成String,而是转成Object了,我们知道 list.get(0)  的实际内容是Integer,Integer转换成Object当然是没有问题的了,因此编译通过了。
注:上面不是调用toString方法的问题,调用其他方法也一样,如调用getClass()

        在此,我只是锁定了问题的所在,我并不知道为什么直接调用 list.get(0) 对象的方法的时候Java内部要尝试把它转换为String,哎,可惜!!








作者: 戴振良    时间: 2014-1-6 20:57
本帖最后由 戴振良 于 2014-1-6 21:01 编辑
范二青年 发表于 2014-1-5 22:25
可以参考这个博客http://bbs.csdn.net/topics/390302204
别看前面给分的  都是扯淡 没用的
看一下最后一条 ...

高手new 了不同参数化的泛型集合出来,有如下一些集合:
ArrayList<Integer> arr1 = new ArrayList<Integer>();
ArrayList<Object> arr3 = new ArrayList<Object>();
ArrayList<Test3> arr4 = new ArrayList<Test3>();
ArrayList arr5 = new ArrayList();
ArrayList<String> arr2 = new ArrayList<String>();
刚开始我搞不懂他为什么要new这么多,直接new一个 ArrayList<String>();的集合不就可以了,这样字节码也没这么多,好分析,现在我知道了,它是为了对比,结果,我们看他的ArrayList<String> arr2 = new ArrayList<String>();是最后new的,具体代码如下:
ArrayList<String> arr2 = new ArrayList<String>();
arr2.add("abc");
System.out.println(arr2.get(0));
因为这3句代码是最后的语句,因此它的字节码也是在最后的,这里只看输出语句对应的字节码,我们看arr2.get(0)这句代码是对应第83行的字节码,System.out.println()方法调用的是第86行的字节码,但是我们看到在arr2.get(0)的字节码后面,即85行有一个检查转换的语句,它是要转换成String。


高手new 了很多其他参数化类型的泛型集合,是要用来做对比,通过对比得知其他参数化的泛型集合都没有如上图85行的转换的语句。

到了这里,也是更进一步的知道了深一点,但是具体原因还是不知道的,如:为什么ArrayList<String>的集合在调用get(i)方法返回的对象的方法时,Java内部为什么要先把这个对象转换为String?如System.out.println(arr2.get(0));语句中,实则上异常是出在arr2.get(0).toString()方法中,与打印语句无关。不仅是调用toString方法有问题,就是调用arr2.get(0)这个对象的其他任何方法,Java内部都会先把arr2.get(0)对象先转换为String。举例如下:
ArrayList<String> list = newArrayList<String>();
Method addMethod =list.getClass().getMethod("add", Object.class);
addMethod.invoke(list, 1);
//    list.get(0).getClass();
//    list.get(0).equals("a");
//    list.get(0).hashCode();
//    list.get(0).wait();
//    list.get(0).notify();

总结就是,String类型的集合,你通过反射无论存了什么类型的数据进去,如Integer、Date、Person等等任何的类,在调用取出来的对象的方法时都会先转换成String。没必要去深竟这个异常的原因了,因为只有String类型的集合够古怪,其他类型的不会,或者就直接认为这是Java语言中的一个Bug,刚开始以为查找这个异常的原因能学到些什么,现在好像什么也没学到,不过多了个见识,就是那字节码是怎么搞出来的?高手还能用字节码找原因啊!





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