答复: Java得到泛型类型

转自Java得到泛型类型的回复,内容有细微调整。 

Java泛型有这么一种规律: 
位于声明一侧的,源码里写了什么到运行时就能看到什么; 
位于使用一侧的,源码里写什么到运行时都没了。 

什么意思呢?“声明一侧”包括泛型类型(泛型类与泛型接口)声明、带有泛型参数的方法和域的声明。注意局部变量的声明不算在内,那个属于“使用”一侧。 java

  1. import java.util.List;  
  2. import java.util.Map;  
  3.   
  4. public class GenericClass<T> {                // 1  
  5.     private List<T> list;                     // 2  
  6.     private Map<String, T> map;               // 3  
  7.       
  8.     public <U> U genericMethod(Map<T, U> m) { // 4  
  9.         return null;  
  10.     }  
  11. }  

上面代码里,带有注释的行里的泛型信息在运行时都还能获取到,原则是源码里写了什么运行时就能获得什么。针对1的GenericClass<T>,运行时经过Class.getTypeParameters()方法获得的数组能够获取那个“T”;同理,2的T、3的java.lang.String与T、4的T与U均可以得到。源码文本里写的是什么运行时就能获得什么;像是T、U等在运行时的实际类型是获取不到的。 

这是由于从Java 5开始class文件的格式有了调整,规定这些泛型信息要写到class文件中。以上面的map为例,经过javap来看它的元数据能够看到记录了这样的信息: 数组

private java.util.Map map;
  Signature: Ljava/util/Map;
  Signature: length = 0x2
   00 0A


乍一看,private java.util.Map map;不正好显示了它的泛型类型被擦除了么? 
但仔细看会发现有两个Signature,下面的一个有两字节的数据,0x0A。到常量池找到0x0A对应的项,是: 数据结构

const #10 = Asciz       Ljava/util/Map<Ljava/lang/String;TT;>;;


也就是内容为“Ljava/util/Map<Ljava/lang/String;TT;>;”的一个字符串。 
根据Java 5开始的新class文件格式规范,方法与域的描述符增添了对泛型信息的记录,用一对尖括号包围泛型参数,其中普通的引用类型用“La/b/c/D;”的格式记录,未绑定值的泛型变量用“Txxx;”的格式记录,其中xxx就是源码中声明的泛型变量名。类型声明的泛型信息也以相似下面的方式记了下来: app

public class GenericClass extends java.lang.Object
  Signature: length = 0x2
   00 12
// ...
const #18 = Asciz       <T:Ljava/lang/Object;>Ljava/lang/Object;;


详细信息请参考官方文档:http://java.sun.com/docs/books/jvms/second_edition/ClassFileFormat-Java5.pdf 
该文档也将会被整合到JVM规范第三版中。惋惜第三版如今只有草案,最终版本何时发布还遥遥无期。 


相比之下,“使用一侧”的泛型信息则彻底没有被保留下来,在Java源码编译到class文件后就确实丢失了。也就是说,在方法体内的泛型局部变量、泛型方法调用之类的泛型信息编译后都消失了。 jvm

import java.util.ArrayList;
import java.util.List;

public class TestClass {
    public static void main(String[] args) {
        List<String> list = null;       // 1
        list = new ArrayList<String>(); // 2
        for (int i = 0; i < 10; i++) ;
    }
}


上面代码中,1留下的痕迹是:main()方法的StackMapTable属性里能够看到: orm

StackMapTable: number_of_entries = 2
 frame_type = 253 /* append */
   offset_delta = 12
   locals = [ class java/util/List, int ]
 frame_type = 250 /* chop */
   offset_delta = 11


但这里是没有留下泛型信息的。这段代码只因此写了个空的for循环就是为了迫使javac生成那个StackMapTable,让1多留个影。当整个方法只有一个基本块的时候javac就不会生成StackMapTable属性,就看不到这可爱的数据结构了。 
若是main()里用到了list的方法,那么那些方法调用点上也会留下1的痕迹,例如若是调用list.add("");,则会留下“java/util/List.add:(Ljava/lang/Object;)Z”这种记录。 
2留下的是“java/util/ArrayList."<init>":()V”,一样也丢失了泛型信息。 

由上述讨论可知,想对带有未绑定的泛型变量的泛型类型获取其实际类型是不现实的,由于class文件里根本没记录实际类型的信息。以为这句话太拗口的话用例子来理解:要想对java.util.List<E>获取E的实际类型是不现实的,由于List.class文件里只记录了E,却没记录使用List<E>时E的实际类型。 
想对局部变量等“使用一侧”的已绑定的泛型类型获取其实际类型也不现实,一样是由于class文件中根本没记录这个信息。例子直接看上面讲“使用一侧”的就能够了。 

知道了什么信息有记录,什么信息没有记录以后,也就能够省点力气不去纠结“拿不到T的实际类型”、“建不出T类型的数组”、“不能对T类型作instanceof”之类的问题了orz接口

相关文章
相关标签/搜索