Java 运行时如何获取泛型参数的类型

LuaPogue 7年前
   <p>在 Java 中对于下面最简单的泛型类</p>    <pre>  <code class="language-java">class A<T> {   public void foo() {    //如何在此处获得运行时 T 的具体类型呢?   }  }</code></pre>    <p>设想我们使用时</p>    <p>new A<String>().foo();</p>    <p>是否能在 foo() 方法中获得当前的类型是 String 呢?答案是否定的,不能。在 foo() 方法中 this 引用给不出类型信息, this.getClass() 就更不可能了,因为 Java 的泛型不等同于 C++ 的模板类, this.getClass() 实例例是被所有的不同具体类型的 A 实例(new A<String>(), new A<Integer>() 等) 共享的,所以在字节码中类型会被擦除到上限。</p>    <p>我们可以在 IDE 的调试时看到这个泛型类的签名</p>    <p><img src="https://simg.open-open.com/show/238664313ddea80dfb398e46292a8161.png"></p>    <p>或者用 javap -v cc.unmi.A 可以查看到类 A 的泛型签名</p>    <p>Signature: #17                          // <T:Ljava/lang/Object;>Ljava/lang/Object;</p>    <p>为什么说是擦除到上限呢?并不是泛型在字节码中都表示为 Object , 看下面的例子,假如 A 声明如下</p>    <p>class A<T extends Number> {</p>    <p>}</p>    <p>再用 javap -v cc.unmi.A 来看泛型签名</p>    <p>Signature: #18                          // <T:Ljava/lang/Number;>Ljava/lang/Object;</p>    <p>也就是说在上面的 foo() 方法中无法获得当前的类型,我们必须给它加个参数 T</p>    <p>public void foo(T t) {</p>    <p>t.getClass();</p>    <p>}</p>    <p>了解了 Java 泛型机制是如何擦除类型的,我们接下来的问题就是如何通过反射获得泛型签名中的类型,一般会在继承或实现泛型接口时会用到它。</p>    <h3>继承一个泛型基类</h3>    <pre>  <code class="language-java">package cc.unmi;    class A<T, ID> {  }    class B extends A<String, Integer> {  }    public class Generic {        public static void main(String[] args) {          System.out.println(B.class.getGenericSuperclass());      }  }</code></pre>    <p>上面的代码输出是</p>    <p>cc.unmi.A<java.lang.String, java.lang.Integer></p>    <p>所以要获得这两个类型是可行的,设置了断点</p>    <p><img src="https://simg.open-open.com/show/3f6231dd5a1c00564c64ca89b41c2e3d.png"></p>    <p>这张图可以看到 B.class.getGenericSuperclass() 得到的实际类型是 ParameterizedTypeImpl 通过它就可以获得 actualTypeArguments 了。代码就是</p>    <pre>  <code class="language-java">ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericSuperclass();  Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  for(Type actualTypeArgument: actualTypeArguments) {      System.out.println(actualTypeArgument);  }</code></pre>    <p>上面的代码输出</p>    <p>class java.lang.String</p>    <p>class java.lang.Integer</p>    <p>我们不妨用 javap -v cc.unmi.B 的泛型签名</p>    <p>Signature: #12                          // Lcc/unmi/A<Ljava/lang/String;Ljava/lang/Integer;>;</p>    <h3>实现一个泛型接口</h3>    <p>这时与继承一个泛型基类的情况略有不同,如下关系,A 是一个泛型接口</p>    <pre>  <code class="language-java">interface A<T, ID> {  }    class B implements A<String, Integer> {  }</code></pre>    <p>该如何反射获得 B 的参数类型呢,用上面的方法已不可行, B.class.getGenericSuperclass() 已不是一个 ParameterizedTypeImpl 而是一个 Object 类型。现在需要另一个方法 getGenericInterfaces(): Type[] 它得到一个 Type 数组,代表类实现的多个接口,因为我们这儿只实现了一个接口,所以取第一个元素,它的类型是我们已见过的 ParameterizedTypeImpl ,</p>    <p><img src="https://simg.open-open.com/show/fdd721ded7012b478b889d52b8e5bf39.png"></p>    <p>因此我们用来获得实现接口而来的泛型参数的代码就是</p>    <pre>  <code class="language-java">ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericInterfaces()[0];  Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  for (Type actualTypeArgument : actualTypeArguments) {      System.out.println(actualTypeArgument);  }</code></pre>    <p>同样能得到上面的一样的结果。</p>    <h3>总结一下</h3>    <ol>     <li>如果是继承基类而来的泛型,就用 getGenericSuperclass() , 转型为 ParameterizedType 来获得实际类型</li>     <li>如果是实现接口而来的泛型,就用 getGenericInterfaces() , 针对其中的元素转型为 ParameterizedType 来获得实际类型</li>     <li>我们所说的 Java 泛型在字节码中会被擦除,并不总是擦除为 Object 类型,而是擦除到上限类型</li>     <li>能否获得想要的类型可以在 IDE 中,或用 javap -v <your_class>   来查看泛型签名来找到线索</li>    </ol>    <p> </p>    <p>来自:http://unmi.cc/java-how-to-get-generic-type/</p>    <p> </p>