java泛型type体系整理-阿里云开发者社区

开发者社区> 愤怒的苹果> 正文

java泛型type体系整理

简介:
+关注继续查看

一直对jdk的ref使用比较模糊,早上花了点时间简单的整理了下,也帮助自己理解一下泛型的一些处理。

 

java中class,method,field的继承体系

 

 

java中所有对象的类型定义类Type


 

说明:

   Type :  Type is the common superinterface for all types in the Java programming language. These include raw types, parameterized types, array types, type variables and primitive types.

 

使用

  一般我们不直接操作Type类型,所以第一次使用会对这个比较陌生,相对内部的一些概念。

 

  根据Type类型分类,整理了一个type -> class的转换过程,同理也包括处理Generic Type。支持多级泛型处理。


1.private static Class<?> getClass(Type type, int i) {  
2.        if (type instanceof ParameterizedType) { // 处理泛型类型  
3.            return getGenericClass((ParameterizedType) type, i);  
4.        } else if (type instanceof TypeVariable) {  
5.            return (Class<?>) getClass(((TypeVariable) type).getBounds()[0], 0); // 处理泛型擦拭对象<R>  
6.        } else {// class本身也是type,强制转型  
7.            return (Class<?>) type;  
8.        }  
9.    }  
10.  
11.    private static Class<?> getGenericClass(ParameterizedType parameterizedType, int i) {  
12.        Object genericClass = parameterizedType.getActualTypeArguments()[i];  
13.        if (genericClass instanceof ParameterizedType) { // 处理多级泛型  
14.            return (Class<?>) ((ParameterizedType) genericClass).getRawType();  
15.        } else if (genericClass instanceof GenericArrayType) { // 处理数组泛型  
16.            return (Class<?>) ((GenericArrayType) genericClass).getGenericComponentType();  
17.        } else if (genericClass instanceof TypeVariable) { // 处理泛型擦拭对象<R>  
18.            return (Class<?>) getClass(((TypeVariable) genericClass).getBounds()[0], 0);  
19.        } else {  
20.            return (Class<?>) genericClass;  
21.        }  
22.    }  

测试代码:


1.interface GeneircInteface<T> {  
2.  
3.    T method1(T obj);  
4.}  
5.  
6.interface CommonInteface {  
7.  
8.    Integer method2(Integer obj);  
9.}  
10.  
11.class BaseGeneircInteface<R> implements GeneircInteface<R> {  
12.  
13.    protected R result;  
14.  
15.    @Override  
16.    public R method1(R obj) {  
17.        return obj;  
18.    }  
19.  
20.}  
21.  
22.class GenericClass extends BaseGeneircInteface<List<String>> implements GeneircInteface<List<String>>, CommonInteface {  
23.  
24.    @Override  
25.    public List<String> method1(List<String> obj) {  
26.        result = obj;  
27.        return result;  
28.    }  
29.  
30.    public Integer method2(Integer obj) {  
31.        return obj;  
32.    }  
33.  
34.    public <T, E extends Throwable> T method3(T obj) throws E {  
35.        return obj;  
36.    }  
37.  
38.}  

针对class的泛型接口使用: 


1.private static void classGeneric() {  
2.        System.out.println("\n--------------------- classGeneric ---------------------");  
3.        GenericClass gc = new GenericClass();  
4.        Type[] gis = gc.getClass().getGenericInterfaces(); // 接口的泛型信息  
5.        Type gps = gc.getClass().getGenericSuperclass(); // 父类的泛型信息  
6.        TypeVariable<?>[] gtr = gc.getClass().getTypeParameters(); // 当前接口的参数信息  
7.        System.out.println("============== getGenericInterfaces");  
8.        for (Type t : gis) {  
9.            System.out.println(t + " : " + getClass(t, 0));  
10.        }  
11.        System.out.println("============== getGenericSuperclass");  
12.        System.out.println(getClass(gps, 0));  
13.        System.out.println("============== getTypeParameters");  
14.        for (TypeVariable t : gtr) {  
15.            StringBuilder stb = new StringBuilder();  
16.            for (Type tp : t.getBounds()) {  
17.                stb.append(tp + " : ");  
18.            }  
19.  
20.            System.out.println(t + " : " + t.getName() + " : " + stb);  
21.        }  
22.  
23.    }  

针对method的泛型接口使用:


1.private static void methodGeneric() throws Exception {  
2.        System.out.println("\n--------------------- methodGeneric ---------------------");  
3.        GenericClass gc = new GenericClass();  
4.        Method method3 = gc.getClass().getDeclaredMethod("method3", new Class[] { Object.class });  
5.  
6.        Type[] gpt3 = method3.getGenericParameterTypes();  
7.        Type[] get3 = method3.getGenericExceptionTypes();  
8.        Type gt3 = method3.getGenericReturnType();  
9.        System.out.println("============== getGenericParameterTypes");  
10.        for (Type t : gpt3) {  
11.            System.out.println(t + " : " + getClass(t, 0));  
12.        }  
13.        System.out.println("============== getGenericExceptionTypes");  
14.        for (Type t : get3) {  
15.            System.out.println(t + " : " + getClass(t, 0));  
16.        }  
17.        System.out.println("============== getType");  
18.        System.out.println(gt3 + " : " + getClass(gt3, 0));  
19.    }  

针对field的泛型接口使用:


1.private static void fieldGeneric() throws Exception {  
2.        System.out.println("\n--------------------- fieldGeneric ---------------------");  
3.        GenericClass gc = new GenericClass();  
4.        Field field = gc.getClass().getSuperclass().getDeclaredField("result");  
5.  
6.        Type gt = field.getGenericType();  
7.        Type ft = field.getType();  
8.        System.out.println("============== getGenericType");  
9.        System.out.println(gt + " : " + getClass(gt, 0));  
10.        System.out.println("============== getType");  
11.        System.out.println(ft + " : " + getClass(ft, 0));  
12.    }  

输出结果:


1.--------------------- classGeneric ---------------------  
2.============== getGenericInterfaces  
3.com.agapple.misc.GeneircInteface<java.util.List<java.lang.String>> : interface java.util.List  
4.interface com.agapple.misc.CommonInteface : interface com.agapple.misc.CommonInteface  
5.============== getGenericSuperclass  
6.interface java.util.List  
7.============== getTypeParameters  
8.  
9.--------------------- fieldGeneric ---------------------  
10.============== getGenericType  
11.R : class java.lang.Object  
12.============== getType  
13.class java.lang.Object : class java.lang.Object  
14.  
15.--------------------- methodGeneric ---------------------  
16.============== getGenericParameterTypes  
17.T : class java.lang.Object  
18.============== getGenericExceptionTypes  
19.E : class java.lang.Throwable  
20.============== getType  
21.T : class java.lang.Object  

结果说明:

 

  1. 因为泛型的擦拭,对应的GeneircInteface和BaseGeneircInteface,在源码信息已被擦除对应的类型,进行了upper转型,所以取到的是Object。可以使用extends
  2. GenericClass在类定义时,声明了继承父接口的泛型为List,所以再通过接口和父类获取泛型信息时,是能正确的获取。通过javap -v可以获取

1.const #46 = Asciz   Lcom/agapple/misc/BaseGeneircInteface<Ljava/util/List<Ljava/lang/String;>;>;Lcom/agapple/misc/GeneircInteface<Ljava/util/List<Ljava/lang/String;>;>;Lcom/agapple/misc/CommonInteface;;  

  1. 而在GenericClass中定义的方法method3,在class信息是一个被向上转型后擦拭的信息。所以获取method3的相关泛型信息是没有的。

1.method3;  
2.const #36 = Asciz   (Ljava/lang/Object;)Ljava/lang/Object;;  
3.const #37 = Asciz   Exceptions;  
4.const #38 = class   #39;    //  java/lang/Throwable  
5.const #39 = Asciz   java/lang/Throwable;  
6.const #40 = Asciz   <T:Ljava/lang/Object;E:Ljava/lang/Throwable;>(TT;)TT;^TE;;  
7.const #41 = Asciz   TT;;  

思考问题:
  1. List<String> list = new ArrayList<String>();  是否有获取对应的String泛型信息?  不能,临时变量不能保存泛型信息到具体class对象中,List<String>和List<Number>对应的class实体是同一个。

1.GeneircInteface gi = new GeneircInteface<Integer>() {  
2.  
3.            @Override  
4.            public Integer method1(Integer obj) {  
5.                return 1;  
6.            }  
7.  
8.        };  

  1.  通过匿名类的方式,是否可以获取Integer的泛型信息?  能,匿名类也会在进行class compiler保存泛型信息。
  2. 假如本文例子中的method3,是放在父类中BaseGeneircInteface中进行申明,GenericClass中指定R为List<String>,是否可以获取到对应的泛型信息?  不能,理由和问题1类似。
备注

具体泛型擦拭和信息保存,引用了撒迦的一段回复,解释的挺详尽了。

 

RednaxelaFX 写道

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来看它的元数据可以看到记录了这样的信息:

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对应的项,是:

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就是源码中声明的泛型变量名。类型声明的泛型信息也以类似下面的方式记了下来:

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文件后就确实丢失了。也就是说,在方法体内的泛型局部变量、泛型方法调用之类的泛型信息编译后都消失了。

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属性里可以看到:

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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
9845 0
Java 集合整理大全
TreeSet, LinkedHashSet and HashSet 的区别 TreeSet的主要功能用于排序,它是无序的(插入顺序)LinkedHashSet的主要功能用于保证FIFO即有序的集合(先进先出)HashSet只是通用的存储数据的集合共同点三者都不是线程安全的,如果要使用线程安全可以collections.
1067 0
Java经典面试题整理及答案详解(三)
以下是某同学面试时,面试官问到的问题,关于面试题答案可以参考以下内容~
6271 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13572 0
Java经典面试题整理及答案详解(九)
本节小编整理了系统架构及项目设计相关的面试题,此类面试题往往考查的是求职者的知识面,所以大家平时要多注意积累,今天小编整理的这15个面试题大家可得掌握呦~
9567 0
+关注
愤怒的苹果
TDDL/DRDS【分布式数据库】 , canal/otter【分布式数据库同步】 yugong【去Oracle数据迁移同步工具】
57
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载