• 详解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。支持多级泛型处理。

    Java代码

     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); // 处理泛型擦拭对象     
     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) { // 处理泛型擦拭对象     
    18             return (Class) getClass(((TypeVariable) genericClass).getBounds()[0], 0);     
    19         } else {     
    20             return (Class) genericClass;     
    21         }     
    22     }    
    23  

    测试代码:

    Java代码

     1 interface GeneircInteface {     
     2     
     3     T method1(T obj);     
     4 }     
     5     
     6 interface CommonInteface {     
     7     
     8     Integer method2(Integer obj);     
     9 }     
    10     
    11 class BaseGeneircInteface implements GeneircInteface {     
    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> implements GeneircInteface<list>, CommonInteface {     
    23     
    24     @Override    
    25     public List method1(List obj) {     
    26         result = obj;     
    27         return result;     
    28     }     
    29     
    30     public Integer method2(Integer obj) {     
    31         return obj;     
    32     }     
    33     
    34     public <t, e < span="">extends Throwable> T method3(T obj) throws E {     
    35         return obj;     
    36     }     
    37     
    38 }     

    针对class的泛型接口使用:

    Java代码

    private static void classGeneric() {     
            System.out.println("
    --------------------- classGeneric ---------------------");     
            GenericClass gc = new GenericClass();     
            Type[] gis = gc.getClass().getGenericInterfaces(); // 接口的泛型信息     
            Type gps = gc.getClass().getGenericSuperclass(); // 父类的泛型信息     
            TypeVariable[] gtr = gc.getClass().getTypeParameters(); // 当前接口的参数信息     
            System.out.println("============== getGenericInterfaces");     
            for (Type t : gis) {     
                System.out.println(t + " : " + getClass(t, 0));     
            }     
            System.out.println("============== getGenericSuperclass");     
            System.out.println(getClass(gps, 0));     
            System.out.println("============== getTypeParameters");     
            for (TypeVariable t : gtr) {     
                StringBuilder stb = new StringBuilder();     
                for (Type tp : t.getBounds()) {     
                    stb.append(tp + " : ");     
                }     
        
                System.out.println(t + " : " + t.getName() + " : " + stb);     
            }     
        
        }    

    针对method的泛型接口使用:

    Java代码

     1 private static void methodGeneric() throws Exception {     
     2         System.out.println("
    --------------------- 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     }    
    20  

    针对field的泛型接口使用:

    Java代码

     1 private static void fieldGeneric() throws Exception {     
     2         System.out.println("
    --------------------- 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     }    

    输出结果:

    Java代码

     1 --------------------- classGeneric ---------------------     
     2 ============== getGenericInterfaces     
     3 com.agapple.misc.GeneircInteface<java.util.list> : 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   
    22  

    结果说明:

    因为泛型的擦拭,对应的GeneircInteface和BaseGeneircInteface,在源码信息已被擦除对应的类型,进行了upper转型,所以取到的是Object。可以使用extends

    GenericClass在类定义时,声明了继承父接口的泛型为List,所以再通过接口和父类获取泛型信息时,是能正确的获取。通过javap -v可以获取对应的class信息

    Java代码

    const #46 = Asciz   Lcom/agapple/misc/BaseGeneircInteface;>;Lcom/agapple/misc/GeneircInteface;>;Lcom/agapple/misc/CommonInteface;;     
     

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

    Java代码

    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   (TT;)TT;^TE;;     
    7 const #41 = Asciz   TT;;    
    8  

    思考问题:

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

    Java代码

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

    通过匿名类的方式,是否可以获取Integer的泛型信息? 能,匿名类也会在进行class compiler保存泛型信息。

    假如本文例子中的method3,是放在父类中BaseGeneircInteface中进行申明,GenericClass中指定R为List,是否可以获取到对应的泛型信息? 不能,理由和问题1类似。

    备注

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

    RednaxelaFX 写道

    Java泛型有这么一种规律:

    位于声明一侧的,源码里写了什么到运行时就能看到什么;

    位于使用一侧的,源码里写什么到运行时都没了。

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

    Java代码

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

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

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

    Javap代码

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

    乍一看,private java.util.Map map;不正好显示了它的泛型类型被擦除了么?

    但仔细看会发现有两个Signature,下面的一个有两字节的数据,0x0A。到常量池找到0x0A对应的项,是:

    Javap代码

    1 const #10 = Asciz Ljava/util/Map;;    

    也就是内容为“Ljava/util/Map;”的一个字符串。

    根据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 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 list = null; // 1      
     7 list = new ArrayList(); // 2      
     8 for (int i = 0; i < 10; i++) ;      
     9 }      
    10 }   

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

    Java代码

    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."":()V”,同样也丢失了泛型信息。

    由上述讨论可知,想对带有未绑定的泛型变量的泛型类型获取其实际类型是不现实的,因为class文件里根本没记录实际类型的信息。觉得这句话太拗口的话用例子来理解:要想对java.util.List获取E的实际类型是不现实的,因为List.class文件里只记录了E,却没记录使用List时E的实际类型。

    想对局部变量等“使用一侧”的已绑定的泛型类型获取其实际类型也不现实,同样是因为class文件中根本没记录这个信息。例子直接看上面讲“使用一侧”的就可以了。

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

  • 相关阅读:
    k8s与监控--k8s部署grafana6.0
    执行kubectl create-f replicaset.yaml后k8s是如何工作的
    Kubernetes+Prometheus+Grafana部署笔记
    Kubernetes Storage Persistent Volumes
    Linux出现假死,怎么回事?
    《算法导论》
    各种算法的核心思想
    Java中 VO、 PO、DO、DTO、 BO、 QO、DAO、POJO的概念
    Java编程思想中关于闭包的一个例子
    Java编程思想第四版随书源码官方下载方法
  • 原文地址:https://www.cnblogs.com/javaleon/p/4292670.html
Copyright © 2020-2023  润新知