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

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 谢洋 高级黑马   /  2013-3-10 21:44  /  9189 人查看  /  29 人回复  /   6 人收藏 转载请遵从CC协议 禁止商业使用本文

public static void main(String[] args){
      //因为编译后就就把泛型类限定去掉了,通过反射获取泛型的参数类型
      //Vector<Date> v1 = new Vector<Date>();
      Method applyMethod = GenericTest.class.getMethod("applyVector",Vector.class);2、//通过从字节码中得到Method对应的方法,这里已经没有<Date>这个信息了
     //可能有多个参数,
     Type[] types=applyMethod.getGenericParameterTypes();
     ParameterizedType pType = (ParameterizedType)types[0];
     //取得原始参数
     System.out.println(pType.getRawType());//class java.util.Vector
    //取得实际类型参数
    System.out.println(pType.getActualTypeArguments()[0]);//class java.sql.Date//3、既然编译后<Data>被去掉了,这里怎么又能拿到?
}
public static void applyVector(Vector<Date> v1){//编译后<Date>信息被去掉
  
}
//编译后相当于
public static void applyVector(Vector v1){//1、编译后已把<Date>信息被去掉
  
}
老师张老师在讲解加强中泛型讲的中示例:
为什么编译后泛型类型参数被去后,通过反射却还可以得到?
反射不是从字节码中得到类相应的成分?而字节码是编译后的东西,是没有泛型信息的,这怎么搞的?

点评

有空的时候,把问题整理下,把整个代码贴出来,以便帮肋其它同学更好理解,谢谢  发表于 2013-3-11 00:54

评分

参与人数 1技术分 +2 收起 理由
admin + 2 这问题很多人问过,我下面回复你.

查看全部评分

29 个回复

倒序浏览
本帖最后由 沈文杰 于 2013-3-10 22:17 编辑

                //通过类的字节码获取所需方法(applyVector)
                Method applyMethod = GenericTest.class.getMethod("applyVector",Vector.class);
                //通过对象反射出所有的带的泛型参数,即Vector<Date>
                Type[] types = applyMethod.getGenericParameterTypes();
                //取出第一个元素,还是Vector<Date>
                ParameterizedType Ptypes = (ParameterizedType)types[0];
                //getActualTypeArguments() 获取到的是<Date>  中所有类型参数,如果是<K,V>则是K,V;取数组中的下标为零的即为Date。
                System.out.println(Ptypes.getActualTypeArguments()[0]);

        }
        public static void applyVector(Vector<Date> v1){
               
        }
回复 使用道具 举报
admin 程序媛 2013-3-11 00:19:26
藤椅
你可以做一个实验,写一个类A和B,如下:

package com.itheima.bean;
public class A<T> {

}

package com.itheima.bean;
public class B {

}

注意,A上有泛形,B上没有,编译后,看它们生成的字节码文件大小:





明显2个字节码文件大小不同(如果A上没有泛形,它们的文件大小是一样的)。这样可以得出一个结论,泛形信息编译后其实是会保存到字节码文件中的。

老张也没说错,他应该是说类上没有泛形了,也就是说字节码文件中不仅有类,也有类的其它信息。java的类装载器在装载字节码文件时单独装载了类(这是出于性能方面的考虑),而把类的泛形信息保存到了其它的地方,所以你在反射时还是反射得到的。但类上的确没有了泛形信息,所以我们会经常看到“擦除”这个词。

如没明白,欢迎继续提问,祝学习愉快。



回复 使用道具 举报 1 0
admin 程序媛 2013-3-11 00:35:13
板凳
再说一句,这问题如果你上班了你可以作面试官问别人,也可以面试时设计一个场景主动跟面试官说出来,外面80%以上的程序员并不明白这问题,这问题困惑了很多人,但很多人脑袋里有过这疑惑,只是一闪就过了,没有深入研究。面试时你能让面试官学到知识,面试效果扛扛的。
回复 使用道具 举报
admin 发表于 2013-3-11 00:19
你可以做一个实验,写一个类A和B,如下:

package com.itheima.bean;

您的泛型参数是T,并不是一个具体的类型,这样依然是无法得到泛型的具体参数类型吧。
其次反射从字节码中读取信息,如果你本身T就没有具体化,那么你依然是不能够得到的。
只有当把这个泛型参数具体化了才能够得到参数的具体类型。
从机制上来说,泛型其实就是强制转型...所以如果你实例化了泛型参数的类型,那么这个类相关字节码中应该就会保存这个泛型参数的具体类型,不然如何强转?
不知我说得对不对

评分

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

查看全部评分

回复 使用道具 举报
admin 程序媛 2013-3-11 01:10:28
地板
mingning179 发表于 2013-3-11 00:56
您的泛型参数是T,并不是一个具体的类型,这样依然是无法得到泛型的具体参数类型吧。
其次反射从字节码中 ...

你理解有问题,特别是“那么这个类相关字节码中应该就会保存这个泛型参数的具体类型”这句话,类的字节码中是没有泛形的具体类型的,程序在运行时,如果确定了T的类型,jvm会把类型信息保存在内存中(不是字节码文件中),所以你能反射到。

类的运行时和编译时这个概念要好好理解。
回复 使用道具 举报
admin 发表于 2013-3-11 01:10
你理解有问题,特别是“那么这个类相关字节码中应该就会保存这个泛型参数的具体类型”这句话,类的字节码 ...

我的意思是那个class文件哈,可能对这个字节码描述不准确。
我的意思是class文件中保存了那些东西。如果class文件中没有具体的类型信息,那么jvm又如何能够加载得到并放在某个地方,让我们能够去反射出来呢?
回复 使用道具 举报
admin 发表于 2013-3-11 01:10
你理解有问题,特别是“那么这个类相关字节码中应该就会保存这个泛型参数的具体类型”这句话,类的字节码 ...

如果没有具体化那么就是Object吧??
回复 使用道具 举报
admin 程序媛 2013-3-11 01:27:26
9#
mingning179 发表于 2013-3-11 01:17
我的意思是那个class文件哈,可能对这个字节码描述不准确。
我的意思是class文件中保存了那些东西。如果c ...

程序要运行,要先把class文件会装到内存中啊,不要老盯着那个文件,文件最终会变成内存中的class,具体类型在程序运行时才确定,也就是在内存中讨论具体类型才有意义,想想

ArrayList<String> arr = new ArrayList<String>()

这句话jvm会怎么执行。

不知道我的解答和你的问题有没有存在不对称现象,最好用代码把你的疑问表述出来,这样避免答非所问。
回复 使用道具 举报
学习了,管理员的讲解很形象,引申:JAVA中遇到的知识点必须要牢牢掌握 黑马必备
回复 使用道具 举报
本帖最后由 mingning179 于 2013-3-11 02:55 编辑
admin 发表于 2013-3-11 01:27
程序要运行,要先把class文件会装到内存中啊,不要老盯着那个文件,文件最终会变成内存中的class,具体类 ...

关于 ArrayList<String> arr = new ArrayList<String>();

第一,如果不对ArrayList这个类做任何改变,你是无法在程序中得到arr这个变量对应的类型的具体泛型参数。如果能请给出具体代码。至于运行时候JVM为什么会知道是String,因为这段代码对应的方法局部变量类型定义区中保存了相关信息。
第二,如果要得到arr这个对应的泛型参数,做法是继承ArrayList并在继承类中给出具体泛参。然后用那个子类来实例化这个arr.这样就可以在程序中通过arr.getclass()去获取具体类型,然后获取泛参了。
这两点不知道是否有问题。
如果没有问题那么第一点说明:虽然那个arr的类型的泛参是String,但是他对应的类中并没有保存那个实际类型。只不过在那段代码所属的方法中有关于那个类型的具体说明。

第二点说明:因为你对Arraylist这个类具体化了,有了一个子类,这个子类中你把泛参具体了。这个arr的运行时类型已经不是arraylist了而是其子类,而这个子类的定义中已经具体了那个泛参,所以你能够通过反射得到。

我表达的意思就是,class文件中如果没有泛型参数的具体类型,那么你是无法得到这个类泛参的具体类型。
如果有保存泛参具体类型,那么你才能够通过反射去得到。虽说我们讨论的是运行时才有意义,但是装载进内存中的字节码是根据class文件进行装载的,所以应该是有一定关联。

这个是arraylist这个类的一个子类,把泛参具体化了的子类。
  1. package cmz.base;

  2. import java.util.ArrayList;
  3. import java.util.Date;


  4. public class DateArray extends ArrayList<Date> {
  5.         @Override
  6.         public Date get(int index) {
  7.                 // TODO Auto-generated method stub
  8.                 return super.get(index);
  9.         }
  10. }
复制代码
编译之后的中间代码..
  1. // Compiled from DateArray.java (version 1.6 : 50.0, super bit)
  2. // Signature: Ljava/util/ArrayList<Ljava/util/Date;>;
  3. public class cmz.base.DateArray extends java.util.ArrayList {
  4.   
  5.   // Method descriptor #6 ()V
  6.   // Stack: 1, Locals: 1
  7.   public DateArray();
  8.     0  aload_0 [this]
  9.     1  invokespecial java.util.ArrayList() [8]
  10.     4  return
  11.       Line numbers:
  12.         [pc: 0, line: 7]
  13.       Local variable table:
  14.         [pc: 0, pc: 5] local: this index: 0 type: cmz.base.DateArray
  15.   
  16.   // Method descriptor #15 (I)Ljava/util/Date;
  17.   // Stack: 2, Locals: 2
  18.   public java.util.Date get(int index);
  19.     0  aload_0 [this]
  20.     1  iload_1 [index]
  21.     2  invokespecial java.util.ArrayList.get(int) : java.lang.Object [16]
  22.     5  checkcast java.util.Date [19]
  23.     8  areturn
  24.       Line numbers:
  25.         [pc: 0, line: 11]
  26.       Local variable table:
  27.         [pc: 0, pc: 9] local: this index: 0 type: cmz.base.DateArray
  28.         [pc: 0, pc: 9] local: index index: 1 type: int
  29.   
  30.   // Method descriptor #18 (I)Ljava/lang/Object;
  31.   // Stack: 2, Locals: 2
  32.   public bridge synthetic java.lang.Object get(int arg0);
  33.     0  aload_0 [this]
  34.     1  iload_1 [arg0]
  35.     2  invokevirtual cmz.base.DateArray.get(int) : java.util.Date [23]
  36.     5  areturn
  37.       Line numbers:
  38.         [pc: 0, line: 1]
  39. }
复制代码
回复 使用道具 举报
学习了,希望以后还有类似的问题时候也能得到解答啊。
回复 使用道具 举报
admin 发表于 2013-3-11 00:19
你可以做一个实验,写一个类A和B,如下:

package com.itheima.bean;

看到你这么解说,我似乎有上点明白,下面是我的对泛型运行的理解,不知这样解释行不行得通
1、以前笔记记下的个人的理解:
既然泛型是给编译器看的,编译后的代码也是确定,也没有泛型信息,那不同类型的实参调用同一份代码是怎么做到?我想是编译在编译时根据实际被不同类型实参调用,就会生成相对应不同的多份代码,这样子程序员只写一份,就可以达到被不同类型的参数调用。这样做好像行得通,但不确实不合理,生产的代码太垃圾,sun公司不可能这样子做吧
2、现在的理解:
根据你所说的以及上面代码执行的效果来看:
a、先首编译给根据被定义了泛型的成分上的泛型信息保存到字节码中的一部分(这里可以说是被编译器看的),然后把去掉泛型信息的源代码编译成字节码中的另一部分(类)。
b、那这两部分是怎么联系起来的?我想泛型信息那部分会以某种方式记住某些成分是始何被定义的,当我们调用实参时,JVM会根据传
过来的实参信息及调用那个成分(应该是地址,这样才能找到相应成分),再到字节码中泛型信息部分查找正在被调用的成分的泛型是如何被定义的,
再根据找泛型信息及实参匹配到正被调用的成分上,也就是说在执行的时才能确定参数类型。
c、另我认为实应用了泛型的成分,编译后是没法确定的参数类型,因为编译根本都没知道参数是什么类型的;
虽然泛信息和类的信息在字节码中是分开的,但这时生成类那部分的代码与普通的代码还是有区别的,那就是普通代码本身就有参数类型信息,而应用泛型的代码没有。
d、最后,如果我上面的理解是正确的,那么这我认为“泛型是给编译器看的,”这句话不是很准确,但为了便于学习,我觉得这么说还是行得通的。

如理解有误之处,强烈希望更正!!
回复 使用道具 举报
admin 程序媛 2013-3-11 10:57:30
14#
mingning179 发表于 2013-3-11 02:44
关于 ArrayList arr = new ArrayList();

第一,如果不对ArrayList这个类做任何改变,你是无法在程序中得 ...


第1、2点表述正确。声明泛形的类中是没有具体类型信息的,使用时才会赋予具体类型,也就是使用者类中(也就是class文件中)会有具体类型的声明。看来咱们的讨论的确存在信息不对称,我指的是泛形类本身,而你指的是赋值的那个类(这种使用者类我在概念上归于运行时)。怪我没细看你的问题。


回复 使用道具 举报
师兄们,小弟学习学习
回复 使用道具 举报
霸道了,这个要学…
回复 使用道具 举报
为自己加油 ,!
回复 使用道具 举报
感觉好深啊。。。看来还得慢慢学习。。。
回复 使用道具 举报
基础很重要啊,继续学习中
回复 使用道具 举报
技术分!!!!!!!!!!!
回复 使用道具 举报
12下一页
您需要登录后才可以回帖 登录 | 加入黑马