`

Java获得泛型类型

 
阅读更多

Java代码  收藏代码
  1. /* 
  2.  * Copyright 2010 Sandy Zhang 
  3.  *  
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
  5.  * use this file except in compliance with the License. You may obtain a copy of 
  6.  * the License at 
  7.  *  
  8.  * http://www.apache.org/licenses/LICENSE-2.0 
  9.  *  
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
  12.  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
  13.  * License for the specific language governing permissions and limitations under 
  14.  * the License. 
  15.  */  
  16.   
  17. /** 
  18.  *  
  19.  */  
  20. package org.javazone.jroi.test.reflect;  
  21.   
  22. import java.lang.reflect.Field;  
  23. import java.util.HashMap;  
  24. import java.util.Map;  
  25.   
  26. /** 
  27.  * @author Sandy Zhang 
  28.  */  
  29. public class Bean  
  30. {  
  31.     public Map<String, ListBean> list = new HashMap<String, ListBean>();  
  32.   
  33.     public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException  
  34.     {  
  35.         Field c = Bean.class.getField("list");  
  36.         Field f = Field.class.getDeclaredField("signature");  
  37.         f.setAccessible(true);  
  38.         System.out.println(((String) f.get(c)));  
  39.   
  40.     }  
  41. }  

这个只是我最近在写一个反射调用的东西想到的问题,所以很无奈才必须要这样得到东西 
下面是结果 
Java代码  收藏代码
  1. Ljava/util/Map<Ljava/lang/String;Lorg/javazone/jroi/test/reflect/ListBean;>;  

字符串都有了,你怕什么呢?呵呵! 

后话:这个方法并不应该被推荐,因为java api下并未提供任何方法实现,也就是说我们必须在特定环境和版本下才能这样干,至少得是1.5之后的版本以后sun(现在是oracle了)准备怎么改,这个是他的说法。。。 
-------------------------------------------------------------------- 
OK,这个是一个引子,从这里我们看到了好些个玩意,也就是java其实提供了获取的方案的 
那么我们要进行私有操作的,这样对我们写代码养成这样的习惯可不好,没事就去拿私有的东西,那是不是应该有公共的方法呢?我们试一下 
将main里的调用修改一下 
Java代码  收藏代码
  1. Field c = Bean.class.getField("list");  
  2. System.out.println(c.toGenericString());  

Java代码  收藏代码
  1. public java.util.Map<java.lang.String, org.javazone.jroi.test.reflect.ListBean> org.javazone.jroi.test.reflect.Bean.list  

看看,我们拿到了什么,不需要setAccess了,呵呵 

哦,对了,你或许要问如果我不是字段呢?是方法怎么办。。于是乎 
Java代码  收藏代码
  1. package org.javazone.jroi.test.reflect;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5.   
  6. /** 
  7.  * @author Sandy Zhang 
  8.  */  
  9. public class Bean  
  10. {  
  11.     public Map<String, ListBean> list = new HashMap<String, ListBean>();  
  12.   
  13.     public Map<String, ListBean> getList()  
  14.     {  
  15.         return list;  
  16.     }  
  17.   
  18.     public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException  
  19.     {  
  20.         System.out.println(Bean.class.getMethod("getList").toGenericString());  
  21.     }  
  22. }  


Java代码  收藏代码
  1. public java.util.Map<java.lang.String, org.javazone.jroi.test.reflect.ListBean> org.javazone.jroi.test.reflect.Bean.getList()  

--------------------------------------------------------------------- 
字符串有了,我们考虑下,API里是不是应该也提供了方法直接获取泛型类型,参考下 
Java代码  收藏代码
  1. getGenericType  
  2. public Type getGenericType()返回一个 Type 对象,它表示此 Field 对象所表示字段的声明类型。   
  3. 如果 Type 是一个参数化类型,则返回的 Type 对象必须准确地反映源代码中使用的实际类型参数。   
  4. 如果底层字段的类型是一个类型变量或者是一个参数化类型,则创建它。否则将解析它。   
  5. 返回:  
  6. 返回表示此 Field 对象所表示字段的声明类型的 Type 对象   
  7. 抛出:   
  8. GenericSignatureFormatError - 如果一般字段签名不符合 Java Virtual Machine Specification, 3rd edition 中指定的格式   
  9. TypeNotPresentException - 如果底层字段的一般类型签名引用了不存在的类型声明   
  10. MalformedParameterizedTypeException - 如果底层字段的一般签名引用了一个因某种原因而无法实例化的参数化类型  
  11. 从以下版本开始:   
  12. 1.5   

由此可见,在1.5之后添加了众多以Generic为关键字的方法,这些方法就是用来获取泛型参数的有效途径,但是或许表面上看不出来,因为他们都是返回的Type接口,而非ParameterizedType接口,所以我困惑了很久。OK,下面我们看一下怎么实现吧 
Java代码  收藏代码
  1. public class GenericTest  
  2. {  
  3.     public List<String> list = new LinkedList<String>();  
  4.   
  5.     public static void main(String[] args) throws SecurityException, NoSuchFieldException  
  6.     {  
  7.         ParameterizedType pt = (ParameterizedType) GenericTest.class.getField(  
  8.                 "list").getGenericType();  
  9.         System.out.println(pt.getActualTypeArguments().length);  
  10.         System.out.println(pt.getActualTypeArguments()[0]);  
  11.     }  
  12. }  



class java.lang.String 
这里是结果 

-___________________________________________________________

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都可以获得。

这是因为从Java 5开始class文件的格式有了调整,规定这些泛型信息要写到class文件中。以上面的map为例,通过javap来看它的元数据可以看到记录了这样的信息:
Javap代码  收藏代码
  1. private java.util.Map map;  
  2.   Signature: Ljava/util/Map;  
  3.   Signature: length = 0x2  
  4.    00 0A  

乍一看,private java.util.Map map;不正好显示了它的泛型类型被擦除了么?
但仔细看会发现有两个Signature,下面的一个有两字节的数据,0x0A。到常量池找到0x0A对应的项,是:
Javap代码  收藏代码
  1. 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就是源码中声明的泛型变量名。类型声明的泛型信息也以类似下面的方式记了下来:
Javap代码  收藏代码
  1. public class GenericClass extends java.lang.Object  
  2.   Signature: length = 0x2  
  3.    00 12  
  4. // ...  
  5. const #18 = Asciz       <T:Ljava/lang/Object;>Ljava/lang/Object;;  

详细信息请参考官方文档:http://java.sun.com/docs/books/jvms/second_edition/ClassFileFormat-Java5.pdf


相比之下,“使用一侧”的泛型信息则完全没有被保留下来,在Java源码编译到class文件后就确实丢失了。也就是说,在方法体内的泛型局部变量、泛型方法调用之类的泛型信息编译后都消失了。
Java代码  收藏代码
  1. import java.util.ArrayList;  
  2. import java.util.List;  
  3.   
  4. public class TestClass {  
  5.     public static void main(String[] args) {  
  6.         List<String> list = null;       // 1  
  7.         list = new ArrayList<String>(); // 2  
  8.         for (int i = 0; i < 10; i++) ;  
  9.     }  
  10. }  

上面代码中,1留下的痕迹是:main()方法的StackMapTable属性里可以看到:
Javap代码  收藏代码
  1. StackMapTable: number_of_entries = 2  
  2.  frame_type = 253 /* append */  
  3.    offset_delta = 12  
  4.    locals = [ class java/util/List, int ]  
  5.  frame_type = 250 /* chop */  
  6.    offset_delta = 11  

但这里是没有留下泛型信息的。这段代码只所以写了个空的for循环就是为了迫使javac生成那个StackMapTable,让1多留个影。
如果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类型的数组”之类的问题了orz
分享到:
评论

相关推荐

    详谈Java8新特性泛型的类型推导

    我们来看一篇关于Java8新特性之泛型的类型推导,希望这篇文章能够让各位深入到了解到关于Java8新特性之泛型的类型用法,有需要的朋友们下面来一起看看吧。

    java使用泛型实现栈结构示例分享

    泛型是Java SE5.0的重要特性,使用泛型编程可以使代码获得最大的重用。由于在使用泛型时要指明泛型的具体类型,这样就避免了类型转换。本实例将使用泛型来实现一个栈结构,并对其进行测试

    Java 泛型(Generics)使用说明

    环境:Windows XP Professional、JDK 1.6、Ant 1.7 说明:Java泛型的动机是为解决类型转换在编译时不报错的问题。另外由于“范型编程”(Generic Programming)的推广,于是2004年JDK 5.0引用范型标准。本例子说明...

    Java泛型和反射.ppt

    泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?...在java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。

    java中获取泛型类的子类

    通过反射获得指定类的父类的泛型参数的实际类型

    泛型dao 泛型dao 泛型dao

    泛型 泛型类型的限定 3.反射 代码概述: bean :Person.java 这个人员类我就不说了 泛型dao接口 :GenericDao, ID extends Serializable&gt; 泛型作为DAO的通用接口 CRUD方法 dao接口 : PersonDAO extends ...

    java基础学习笔记之泛型

    所谓泛型,就是变量类型的参数化。泛型是JDK1.5中一个最重要的...通过引入泛型,我们将获得编译时类型的安全和运行时更小的抛出ClassCastException的可能。在JDK1.5中,你可以声明一个集合将接收/返回的对象的类型。

    JAVA上百实例源码以及开源项目

     Java波浪文字,一个利用Java处理字符的实例,可以设置运动方向参数,显示文本的字符数组,高速文本颜色,显示字体的 FontMetrics对象,得到Graphics实例,得到Image实例,填充颜色数组数据,初始化颜色数组。...

    JAVA上百实例源码以及开源项目源代码

     Java波浪文字,一个利用Java处理字符的实例,可以设置运动方向参数,显示文本的字符数组,高速文本颜色,显示字体的 FontMetrics对象,得到Graphics实例,得到Image实例,填充颜色数组数据,初始化颜色数组。...

    java开源包4

    使用泛型来做强类型编程 多协议扩展支持(REST, RPC, SOAP, etc) Rails3消息队列系统 Sidekiq Sidekiq 为 Rails 3 应用程序提供一个高效的消息队列系统。 Java文件上传组件 COS FAT文件系统读写类库 fat32-lib ...

    java开源包101

    使用泛型来做强类型编程 多协议扩展支持(REST, RPC, SOAP, etc) Rails3消息队列系统 Sidekiq Sidekiq 为 Rails 3 应用程序提供一个高效的消息队列系统。 Java文件上传组件 COS FAT文件系统读写类库 fat32-lib ...

    java开源包11

    使用泛型来做强类型编程 多协议扩展支持(REST, RPC, SOAP, etc) Rails3消息队列系统 Sidekiq Sidekiq 为 Rails 3 应用程序提供一个高效的消息队列系统。 Java文件上传组件 COS FAT文件系统读写类库 fat32-lib ...

    java开源包6

    使用泛型来做强类型编程 多协议扩展支持(REST, RPC, SOAP, etc) Rails3消息队列系统 Sidekiq Sidekiq 为 Rails 3 应用程序提供一个高效的消息队列系统。 Java文件上传组件 COS FAT文件系统读写类库 fat32-lib ...

    java开源包9

    使用泛型来做强类型编程 多协议扩展支持(REST, RPC, SOAP, etc) Rails3消息队列系统 Sidekiq Sidekiq 为 Rails 3 应用程序提供一个高效的消息队列系统。 Java文件上传组件 COS FAT文件系统读写类库 fat32-lib ...

    java开源包8

    使用泛型来做强类型编程 多协议扩展支持(REST, RPC, SOAP, etc) Rails3消息队列系统 Sidekiq Sidekiq 为 Rails 3 应用程序提供一个高效的消息队列系统。 Java文件上传组件 COS FAT文件系统读写类库 fat32-lib ...

    java开源包10

    使用泛型来做强类型编程 多协议扩展支持(REST, RPC, SOAP, etc) Rails3消息队列系统 Sidekiq Sidekiq 为 Rails 3 应用程序提供一个高效的消息队列系统。 Java文件上传组件 COS FAT文件系统读写类库 fat32-lib ...

    java开源包5

    使用泛型来做强类型编程 多协议扩展支持(REST, RPC, SOAP, etc) Rails3消息队列系统 Sidekiq Sidekiq 为 Rails 3 应用程序提供一个高效的消息队列系统。 Java文件上传组件 COS FAT文件系统读写类库 fat32-lib ...

    java jdk实列宝典 光盘源代码

    获得数据库和表的元数据Getmetadata.java; 查询和更新数据库OperateDatadb.java; 批处理,介绍如何一次执行一批sql语句,这些sql语句用插入、更新和删除等相关操作; 提交和回滚transaction.java; 使用...

    疯狂JAVA讲义

    8.4.2 泛型方法和类型通配符的区别 306 8.4.3 设定通配符的下限 307 8.4.4 泛型方法与方法重载 309 8.5 擦除和转换 310 8.6 泛型与数组 311 8.7 本章小结 313 第9章 与运行环境交互 314 9.1 与用户互动 315 ...

Global site tag (gtag.js) - Google Analytics