Retrofit解析3之反射

本篇文章的主要内容如下:

  • 1、什么是反射和反射机制
  • 2、什么是Java反射
  • 3、Java反射可以做什么
  • 4、反射机制的优缺点
  • 5、Java类加载原理
  • 6、核心类及API
  • 7、Method的invoke原理解析
  • 8、泛型与反射

一、什么是反射与反射机制

(一)、什么是反射

反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。在计算机科学领域,反射是一类应用,它们能够自描述和自控制。这类应用通过某种机制来实现对自己行为的描述和检测,并根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

(二)、反射机制

反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法功能称为反射机制

二、什么是Java反射

Java 反射是Java语言的一个很重要的特征,它使得Java具备了"动态化"。

在Java中的反射机制,被称为Reflection。它允许运行中的Java程序对自身进行检测,并能直接操作程序的内部属性或方法。Reflection机制允许程序在正在执行的过程中,利用Reflection APIs取得任何已知名称的类的内部信息。包括如下:package、 type parameters、 superclass、 implemented interfaces、 inner classes、 outer classes、 fields、 constructors、 methods、 modifiers等,并可以在执行过程中,动态生成Instances、变更fields内容或唤起methods。

通俗的讲就是

.class反编译-->.java,这样就可以通过反射机制访问Java对象的属性,方法,构造方法等。

三、Java反射可以做什么?

Java反射机制主要提供以下功能:

  • 运行时判断任意一个对象所属的类
  • 在运行时构造任意个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用人一个对象的方法
  • 生成动态代理

四、反射机制的优缺点:

为什么要用反射机制,直接创建对象不就可以了吗?这就涉及到了动态与静态的概念。

  • 静态编译:在编译时确定类型,绑定对象,即通过。
  • 动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了Java的灵活性,体现了多态的应用,有以降低类之间的耦合性。

优缺点

  • 1、优点: 可以实现动态创建对象和编译,体现出很大的灵活性,特别是J2EE的开发中它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把它设计的很完美,当这个程序编译,发布了,当发现需要更新某些功能时,我们不可能要用户把之前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时都才动态的创建和编译,就可以显示该功能。
  • 2、缺点: 对性能有影响,使用反射基本上是一种解释操作,我可以告诉JVM,我希望做什么并且它满足我们的需求,这类操作总是慢于直接执行相同的操作。

五、Java类加载原理

为了后里面讲解Java反射原理方便,在这里先讲解Java类加载原理

(一)、概述

Class文件由类装载器加载后,在JVM中将形成一份描述Class结构的元数据对象,通过该元数据可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借这个Class相关原信息对象间接调用Class对象的功能。

虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

JVM把class文件加载到内存,并对数据进行校验,解析和初始化,最形成JVM可以直接使用的JAVA类型的过程。

加载-->链接(-->验证-->准备-->解析)-->初始化-->使用-->卸载

1、加载

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。

class字节码-->类加载类--->内存(Class对象,方法区中运行时数据)--->外部可以通过Class对象,操作类

2、链接

将Java类的二进制代码合并到JVM的运行状态之中的过程

  • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题。
  • 准备:正式为类变量(static变量)分配内存并设置变量初始值的阶段,这些内存都将在方法区中进行分配
  • 解析:虚拟机常量池内的符合引用替换为直接引用的过程。
  • 常用吃:虚拟常用池内的符合引用替换为直接引用的过程。
3、初始化

  • 初始化阶段是执行类构造器Class的<clinit>()方法的过程。类构造器<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句(static)块中的语句合并产生的。
  • 当初始化一个类的时候,如果发现其弗雷还没有进行初始化、则需要先执行父类的初始化。
  • 虚拟机会保证一个的类<clinit>()方法在多线程中呗正确的加锁和同步。
  • 当访问一个Java类的静态域时,只有真正声明这个域才会被初始化。

初始化.png

4、使用
5、卸载

使用和卸载没有什么好讲解的,就不说了。

最后附上 别人的思维导图

类加载.png

(二) 其它:

1、这里说下<clinit>和<init>的区别

在编译生成class文件时,会自动产生两个方法,一个是类的初始化方法<clinit>,另一个是实例化的初始化方法<init>

  • <clinit>:在JVM第一次加载class文件时调用,包括静态变量初始化语句和静态块的执行
  • <init>: 在实例创建出来的时候调用,包括调用new操作符;滴啊用Class或者java.lang.reflect.Constructor对象的newInstance()方法;调用任何现有对象的clone()方法;通过java.io.ObjectInputStream类的getObject()方法反序列化。
2、主动引用和被动引用
主动引用:一定会发生类的初始化

  • new一个类的对象
  • 调用类的静态成员(除了final常量)和静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当虚拟机启动,java helloworld,则一定会初始化helloworld类,说白了就是先启动main方法所在的类。
  • 当初始化一个类,如果其父类没有被初始化,则会先初始化他的父类。
被动引用:不会发生类的初始化

  • 当访问一个静态域时,

当访问一个静态域时,只有真正声明这个域的类才会被初始化 通过子类引用父类的静态变量,不会导致子类初始化 通过数组定义类引用,不会触发此类的初始化 引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)

六、核心类及API

核心类,位于java.lang.reflect包中

  • Class类:代表一个类
  • Field类:代表一个类
  • Method类:代表类的方法
  • Constructor类:代表类的构造方式
  • Array类:提供了动态创建数组,以及访问数组的元素的静态方法

(一)Class

1、Class是什么?

Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标记,这项信息记录了每个对象所属的类。虚拟机通常使用运行时类型信息选择正确方法去执行,用来保存这些类型的类是Class类。

Class类封装一个对象和接口运行时的状态,当加载类时,Class类型的对象自动创建。Class没有公共构造方法。Class对象是在加载类时由Java虚拟机以及通过调用类加载器中的defineClass方法自动构造的,因此不能显示地声明一个Class对对象。

虚拟机为每种类型管理一个独一无二的Class对象。也就是说,每个类(型)都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象加载。

基本的Java类型(boolean、byte、char、shor、int、long、float、double)和关键字void也都对应一个Class对象。每个数据属于被映射为Class对象的一个类,所有具有相同元素类型和维数的数组都 共享该Class对象。一般某个类Class对象被载入内存,它就用来创建这个类的所有对象。

2、Class类的常用方法

在java.lang.Object类中定义了getClass()方法,因此对于任意一个Java对象,都可以通过此方法获得对象的类型。Class类是Reflection API中的核心类。它有如下方法:

方法名

作用

getName()

获得类的完整名称

getFields()

获得类的public类型的属性

getDeclaredFields()

获得类的所有属性

getMethod()

获得类的public类型方法

getDeclaredFiedMethods()

获得类的所有方法

getMethod(String name,Class[] parameterTypes)

获得类的特定方法,name参数制定方法的名字,parameterTypes 参数制定方法参数类型

getConstructors()

获得类的public类型的构造方法

getConstructor(Class[] parameterTypes)

获得类的特定构造方式,parameterTypes 参数指定构造方法的参数类型

newInstance()

通过类的不带参数的构造方法创建这个类的一个对象

PS:

  • 大家在使用Class实例化其他类的对象的时候,一定要自己定义午餐的构造函数
  • 所有类的对象其实都得Class的实例
3、获取Class对象的三种方式

  • 通过Object类的getClass()方法 例如 Class c1=new String("hello world").getClass();
  • 通过Class类的静态方法——forName()来实现: Class c2 = Class.forName("MyObject");
  • 如果T是一个已定义的类型的话,在java中,它的.class文件名:T.class就代表了与其匹配的Class对象。例如: Class c3 = Manager.class; Class c4 = int.class; Class c5 = Double[].class;

(二)、Constructor

Constructor 提供关于类的单个构造方法的信息以及对它的访问权限,用来封装反射得到的构造器。

1、获取构造方法 Class 类提供四个public方法,用于获取某个类的构造方法

方法名

作用

Constructor getConstructor(Class[] params)

根据构造函数的参数,返回一个具体的具有public属性的构造函数

Constructor[] getConstructor()

返回 所有具有public属性的构造函数数组

Constructor getDeclaredConstructor(Class[] params)

根据构造函数的参数,返回一个具体的构造函数(不分public和非public 属性)

Constructor getDeclaredConstrutors()

返回该类中所有的构造函数数组(不分public 和非public属性)

由于Java语言是一种面向对象的语言,具有多态的性质,那么我们可以通过构造方法的参数列表的不同,来调用不同的构造方法去创建类的实例。同样,获取不同的构造方法的信息,也需要提供与之对应的参数类型信息;因此,就产生了以上四种不同的获取构造方法的方式。

(三)、Field 成员变量信息

想一想成员变量中都包含什么:

成员变量类型+成员变量名

类的成员变量也是一个对象,它是java.lang.reflect.Field的一个对象,所以我们通过java.lang.reflect.Field里面封装的方法来获取这些信息。

如果想单独获取某个成员变量,通过Class类的以下方法实现:

方法名

作用

Field getDeclaredField(String name)

获得该类自身声明的所有变量,不包括其父类的变量

Field getField(String name)

获得该类自所有的public成员变量,包括其父类变量

举例如下: 参数是成员变量的名字。 例如一个类A有如下成员变量:

private int n;

如果A有一个对象a,那么就可以这样得到其成员变量:

Class c = a.getClass();
Field field = c.getDeclaredField("n");

由于Method 在Retrofit比较重要,我们就单独讲解以下

(四) Method类及invoke

Method 提供关于类或接口上单独某个方法(以及如何反问该方法)的信息,一个完整方法包含的属性有:方法上使用的注解、方法的修饰符、方法上定义的泛型参数、方法的返回值、方法名称、方法抛出的异常。

1、获取Method

有4种获取Method的方式:

方法名

作用

Method getMethod(String name, Class[] params)

根据方法名和参数,返回一个具体的具有public属性的方法

Method[] getMethods()

返回所有具有public属性的方法数组

Method getDeclaredMethod(String name, Class[] params)

根据方法名和参数,返回一个具体的方法(不分public和非public属性)

Method[] getDeclaredMethods()

返回该类中的所有的方法数组(不分public和非public属性)

PS:在获取类的方法时,有一个地方值得大家注意,就是getMethods()方法和getDeclaredMethods()方法。 在

  • getMethods():用于获取类的所有的public修饰域的成员方法,包括从父类继承的public方法和实现接口的public方法
  • getDeclaredMethods():用于获取当前类中定义的所有的成员方法和实现接口的方法,不包括从父类继承的方法。
2、Method的API

那来看下Method的API,因为Retrofit里面大量用到了Method的api

方法名

作用

<T extends Annotation> T getAnnotation(Class<T> annotationClass)

如果存在该元素的指定类型的注释,则返回这些注解,否则返回 null

Annotation[] etDeclaredAnnotations()

返回直接存在于此元素上的所有注解

Class<?> getDeclaringClass()

返回表示声明由此 Method 对象表示的方法的类或接口的 Class 对象

Object getDefaultValue()

返回由此 Method 实例表示的注解成员的默认值。

Class<?>[] getExceptionTypes()

返回 Class 对象的数组,这些对象描述了声明将此 Method 对象表示的底层方法抛出的异常类型。

Type[] getGenericExceptionTypes()

返回 Type 对象数组,这些对象描述了声明由此 Method 对象抛出的异常。

Type[] getGenericParameterTypes()

按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的。

Type getGenericReturnType()

返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象

int getModifiers()

以整数形式返回此 Method 对象所表示方法的 Java 语言修饰符。

String getName()

以 String 形式返回此 Method 对象表示的方法名称。

Annotation[][] getParameterAnnotations()

返回表示按照声明顺序对此 Method 对象所表示方法的形参进行注释的那个数组的数组。

Class<?>[] getParameterTypes()

按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。

Class<?> getReturnType()

返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型。

TypeVariable<Method>[] getTypeParameters()

返回 TypeVariable 对象的数组,这些对象描述了由 GenericDeclaration 对象表示的一般声明按声明顺序来声明的类型变量

Object invoke(Object obj, Object... args)

对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。

补充一个知识点: 在反射机制中,Field的getModifiers()方法返回int类型值表示该字段的修饰符,其中该修饰符是java.lang.reflect.Modifier的静态属性。对应如下: Modifier.java

 /**
     * The {@code int} value representing the {@code public}
     * modifier.
     */
    public static final int PUBLIC           = 0x00000001;

    /**
     * The {@code int} value representing the {@code private}
     * modifier.
     */
    public static final int PRIVATE          = 0x00000002;

    /**
     * The {@code int} value representing the {@code protected}
     * modifier.
     */
    public static final int PROTECTED        = 0x00000004;

    /**
     * The {@code int} value representing the {@code static}
     * modifier.
     */
    public static final int STATIC           = 0x00000008;

    /**
     * The {@code int} value representing the {@code final}
     * modifier.
     */
    public static final int FINAL            = 0x00000010;

    /**
     * The {@code int} value representing the {@code synchronized}
     * modifier.
     */
    public static final int SYNCHRONIZED     = 0x00000020;

    /**
     * The {@code int} value representing the {@code volatile}
     * modifier.
     */
    public static final int VOLATILE         = 0x00000040;

    /**
     * The {@code int} value representing the {@code transient}
     * modifier.
     */
    public static final int TRANSIENT        = 0x00000080;

    /**
     * The {@code int} value representing the {@code native}
     * modifier.
     */
    public static final int NATIVE           = 0x00000100;

    /**
     * The {@code int} value representing the {@code interface}
     * modifier.
     */
    public static final int INTERFACE        = 0x00000200;

    /**
     * The {@code int} value representing the {@code abstract}
     * modifier.
     */
    public static final int ABSTRACT         = 0x00000400;

    /**
     * The {@code int} value representing the {@code strictfp}
     * modifier.
     */
    public static final int STRICT           = 0x00000800;

简单一点就是:

  • PUBLIC: 1
  • PRIVATE: 2
  • PROTECTED: 4
  • STATIC: 8
  • FINAL: 16
  • SYNCHRONIZED: 32
  • VOLATILE: 64
  • TRANSIENT: 128
  • NATIVE: 256
  • INTERFACE: 512
  • ABSTRACT: 1024
  • STRICT: 2048
3、Method的invoke方法
注意事项1

通过Method对象调用invoke()方法

//获取一个方法名为doHello,参数类型为String的方法
Method method = MyObject.class.getMethod("doHello", String.class);
Object returnValue = method.invoke(null, "value1");

如果是一个静态方法调用的话,可以穿用null代替制定对象作为invoke()方法的参数,在上面的方法中,如果doHello不是静态方法的话,你就要传入有效的MyObject对象,而不是null。

注意事项2

当通过Method的invoke()方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限,如果程序确实需要调用某个对象的private方法,则可以先调用Method对象的如下方法。

setAccessible(boolean flag)

将Method对象的accessible设置为制定的布尔值。值为true,指示Method在使用时应该取消Java语言访问权限检查;值为false,则指示该Method在使用时实施Java语言的访问权限检查。

七、Method的invoke原理解析

先上代码

Class actionClass=Class.forName(“MyClass”);
Object action=actionClass.newInstance();
Method method = actionClass.getMethod(“myMethod”,null);
method.invoke(action,null);

上面就是最常见的反射使用的例子,前两行实现了类的装载、链接和初始化(newInstance方法实际上也是使用反射调用了<init>方法),后两行实现了从class对象中获取到method对象然后执行反射调用。下面简单分析一下后两行的原理。

Class Method{
     public Object invoke(Object obj,Object[] param){
        MyClass myClass=(MyClass)obj;
        return myClass.myMethod();
     }
}

method.invoke(action,null);的原理其实就是动态的生成类似于上面的字节码,加载到JVM中运行。

1、获取Method对象

首先来看下Method对象是如何生成的。

Method生成.png

上面的Class对象是在加载类时由JVM构造的,JVM为每个类管理一个独一无二的Class对象,这份Class对象里面维护着该类的所有Method,Field,Constructor的cache,这份cache也可以被称为根对象。每次getMethod获取到的Method对象都持有对跟对象的引用,因此一些重量级别的Method的成员变量(主要是MethodAccessor),我们不希望每次创建Method都要重新初始化,于是所有代表同一个方法的Method对象都共享根对象的MethodAccessor,每一次创建都会调用对象的copy方法复制一份:

    Method copy() {
        if(this.root != null) {
            throw new IllegalArgumentException("Can not copy a non-root Method");
        } else {
            Method var1 = new Method(this.clazz, this.name, this.parameterTypes, this.returnType, this.exceptionTypes, this.modifiers, this.slot, this.signature, this.annotations, this.parameterAnnotations, this.annotationDefault);
            var1.root = this;
            var1.methodAccessor = this.methodAccessor;
            return var1;
        }
    }
2、调用invoke()方法

获取到Method对象之后,调用invoke方法的流程如下:

调用invoke.png

上代码

    @CallerSensitive
    public Object invoke(Object var1, Object... var2) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        if(!this.override && !Reflection.quickCheckMemberAccess(this.clazz, this.modifiers)) {
            Class var3 = Reflection.getCallerClass();
            this.checkAccess(var3, this.clazz, var1, this.modifiers);
        }

        MethodAccessor var4 = this.methodAccessor;
        if(var4 == null) {
            var4 = this.acquireMethodAccessor();
        }

        return var4.invoke(var1, var2);
    }

代码解释

  • 首先,检查AccessibleObject的overrider属性是否为true。因为AccessibleObject是Method、Field、Constructor的父类,override属性默认为false,可调用setAccessible方法改变,如果设置为true,则表示可以忽略访问权限的限制,直接调用。
  • 其次,如果不是true,则要进行访问权限监测,用Reflection的quickCheckMemberAccess方法检查是不是public,如果不再用Reflection.getCallerClass()方法获得到调用这个方法的Class,然后做是否有权限访问的校验,校验之后缓存一次,以便下次如果还是这个类调用就不用去做校验,直接用上次的结果。(很奇怪用这种方式缓存,因为这种方式如果下次换个类来调用的话,就不会用缓存了,而再验证一遍,把这次的结果作为缓存,但上一次的缓存结果就冲掉了,这是一个很简单的缓存机制,只适用一个类的重复调用。)
  • 再次,调用MethodAccessor的invoke()方法。每个Method对象包含一个root对象,root对象里持有 一个MethodAccessor对象。我么获得的Method独享相当于一个root对象的镜像,所有这类Method共享root李的MethodAccessor对象,(这个对象有ReflectionFactory方法生成,ReflectionFactory对象在Method类中是static final的native方法实例化。)
  • 最后,我们深入一下NativeMethodAccessorImpl的invoke()方法,NativeMethodAccessorImpl的invoke方法里面调用了native方法的invoke执行。可以都看到,调用Method.invoke之后,就会去调用MethodAccessor.invoke().MethodAccessor就是上面提到的所有同名method共享的对象,有ReflectionFactory创建。创建机制采用了一种名为inflation的方式,如果该方法的累计调用次数<=15,会创建出NativeMethodAccessorImpl,它的实现就是直接调用native方法实现反射;如果该方法的累计调用次数>15,会有Java代码创建处于字节码组装而成的MethodAccessorImpl。

PS:是否采用inflation和15这个数字都可以在JVM参数中调整。

总结一下:

一个方法可以生成多个Method对象,但只有一个root对象,主要用持有一个MethodAccessor对象,这个对象也可以认为一个方法只有一个,相当于是static的,因为Method的invoke是交给MethodAccessor执行的,

八、泛型与反射

(一)、什么是泛型

泛型(Generic type 或者 generics) 是对Java语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把来类型参数看做是使用参数化类型时制定的类型的一个占位符,就像方法的形式参数是运行时传递值的占位符一样。

可以在集合框架( Collection frameword ) 中看到泛型的动机。例如,Mapl类允许您向一个Map 添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String) 的对象。因为Map.get()被定义为返回Object,所以一般必须将Map.get()的结果强制类型转换为期望的类型,如下面的代码所示:

Map m = new HashMap();
m.put("key", "hello");
String s = (String) m.get("key");

要让程序通过编译,必须将get()的结果强制类型转换为String,并且希望结果真的是一个String。但是有可能某人已经在映射中保存了不是String的东西,这样的话,上面的代码将会抛出ClassCastException。

理想情况下,你可能会得出这样一个观点,即m是一个Map,它将String键映射到String值。这可以让您消除代码中强制类型转换,同时获得一个附加的类型检查层,该检查层可以防止有人将错误类型的键或值保存在集合中。这样就是泛型所做的工作。

(二)、泛型的好处

Java语言中引入泛型是一个比较大的功能增强。不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,已经成为泛型化,这带来很多好处:

  • 类型安全:泛型的主要目标是提高Java程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程序上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在注释中)。 Java程序中的一种流行技术是定义这样的集合,即它的元素或键是公共类型的,比如"String 列表"或者"String 到 String 的映射"。通过在变量声明中捕获这一附加的类型信息,泛型允许编译器实施这些附加的类型约束。类型错误现在就可以在编译时被捕获了,而不是在运行时当做ClassCastException 展示出来。将类型检查从运行时挪到编译时有助于你更容易找到错误。并提高程序的可靠性。
  • 消除强制类型转换。泛型的一个附带好处是,消除源代码中的许多强制类型转化。这使得代码更加可读,并且减少了出错的机会

(三)、命名类型参数

推荐的命名约定是使用大写的单个字幕作为类型参数。这与C++约定有所不同,并反映了大多数泛型类将具有少量类型参数的假设。对常见的泛型模式,推荐的名称是:

  • K————键,比如映射的键
  • V————值,比如List和Set的内容,或者Map中的值。
  • E————异常类
  • T————泛型

(四)泛型擦除

1、类型擦除

正确理解泛型概念的首要前提是理解类型擦除(type erasure)。Java中泛型基本上都是编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就叫做类型擦除。

比如在代码中定义List<Object>和List<String>等类型,在编译之后就会变成List。JVM看到的是List,而泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍无法避免在运行时出现类型转换异常的情况。类型擦除也是Java泛型实现方法与C++模板机制实现方法是之间的重要区别

注意:

很多泛型的奇怪特性都与这个类型擦除的存在有关,包括泛型类并没有自己独有的Class类对象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class。静态变量是被泛型类的所有对象所共享的。对于声明为MyClass<T>的类,访问其中的静态变量的方法仍然是MyClass.myStaticVar。不管是通过new MyClass<String> 还是new MyClass<Integer> 创建的对象,都是共享一个静态变量。泛型的类型参数不能用在Java异常处理的catch语句中,因为异常处理是由JVM在运行时进行的,由于类型信息被擦除,JVM是无法区分两个异常类型MyExcetption<String>和MyExcetion<Integer>的。对于JVM来说,他们都是MyExcetpion类型。也就是无法执行与异常对应的catch语句

(五)、通配符与上下界

在使用泛型类的时候,既可以指定一个具体的类型,比如List<String> 就声明了具体的类型是String,也可以使用通配符?来表示位置类型,比如List<?>就声明了List中包含的元素类型是未知的。通配符所代表的其实是一组类型,但具体的类型是未知的。List<?> 所声明的就是所有类型都是可以的。但是List<?>并不等同与List<Object>。List<Object>实际上确定了List中包含的是Object及其子类,在使用的时候都可以通过Object来进行引用。而List<?>则表示其中所包含的类型是不确定的。其中可能包含的是String,也可能是Integer。如果它包含了String的话,往里面添加Integer类型的元素就是错误的。正因为类型未知,就不能通过new ArrayList<?>()来创建一个新的ArrayList对象,因为编译器无法知道具体的类型是什么。但是对于List<?>中的元素却总是可以通过Object来引用。因为虽然类型未知,但肯定是Object及其子类。

(六) 泛型的使用注意事项:

在使用泛型的时候可以遵循一些基本的原则,从而避免一些常见的问题。

  • 在代码中避免泛型类和原始类型的混用。比如List<String> 和List不应该共同使用。这样会产生一些编译器警告和潜在的运行时异常。
  • 在使用带通配符的泛型类的时候,需要明确通配符所代表的一组类型的概念。由于具体的类型是未知的,很多操作是不允许的。
  • 泛型最好不要和同数组一块使用。
  • 不要忽视编译器给出的警告信息。

(七) 泛型与反射

1、Java泛型与反射结构

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

Paste_Image.png

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

Paste_Image.png

2、相关类说明
(1)、Type

它 是所有类型的公共接口,包括:

  • 原始类(raw types[对应 Class])
  • 参数化类型(parameterized types [对应 ParameterizedType])
  • 数组类型(array types [ 对应 GenericArrayType])
  • 类型变量(type variables [对应 TypeVariable ])
  • 基本类型((primitivetypes)[对应 Class ])

同时ParameterizedTypeTypeVariableWildcardTypeGenericArrayType这四个接口都是它的子接口。

(2)、GenericDeclaration 接口

1、接口含义:所有可以声明类型变量(TypeVariable)的公共父接口 2、直接实现子类:java.lang.reflect子包中的:这个接口Class、Method、Constructor都有实现 注意:方法的属性上面不能定义类型变量,所以GenericDeclaration的直接实现子类没有Field类

GenericDeclararion接口的源码

package java.lang.reflect;  
public interface GenericDeclaration {  
    public TypeVariable<?>[] getTypeParameters();  
}  

方法的返回值类型是数组,原因就是一个可以声明类型变量的实体可以同时声明多个类型变量,并且每一个元素类型是TypeVariable类型。

(3)、 TypeVariable接口

它表示类型变量。

package java.lang.reflect;
public interface TypeVariable<D extends GenericDeclaration> extends Type {
    /**
     * Returns the upper bounds of this type variable. {@code Object} is the
     * implicit upper bound if no other bounds are declared.
     *
     * @return the upper bounds of this type variable
     *
     * @throws TypeNotPresentException
     *             if any of the bounds points to a missing type
     * @throws MalformedParameterizedTypeException
     *             if any of the bounds points to a type that cannot be
     *             instantiated for some reason
     */
    Type[] getBounds();

    /**
     * Returns the language construct that declares this type variable.
     *
     * @return the generic declaration
     */
    D getGenericDeclaration();

    /**
     * Returns the name of this type variable as it is specified in source
     * code.
     *
     * @return the name of this type variable
     */
    String getName();
}

泛型接口TypeVariable<D extends GenericDeclaration> 可以知道TypeVariable中通过泛型限定extends指定的父类正好的是接口GenericDeclaration。而GenericDeclaration的直接实现子类仅有Class,Method和Constructor,所以TypeVariable的所有参数化类型是

  • TypeVariable<Class>
  • TypeVariable<Method>
  • TypeVariable<Constructor>

由于Class和Constructor本身也是泛型类,但是Method本身不是泛型,所以上面的参数化类型应该是如下:

  • TypeVariable<Class<T>> :Class<T>类上声明的TypeVariable
  • TypeVariable<Method> : Method类上声明的TypeVariable
  • TypeVariable<Constructor<T>> : Constructor类上声明的TypeVariable

所以TypeVariable中的泛型是对定义TypeVariable位置的描述,不同的GenericDeclaration的实现子类代表了这个类型变量到底是在类上定义的,还是方法上定义的,还是在构造上定义的。

现在来分析下三个方法 先定义public class TypeVariableBean<K extends InputStream & Serializable, V> ,其中K ,V 都是属于类型变量。

  • Type[] getBounds():得到上边界的Type数组,如K的上边接数组是InputStream和Serializable。V没有指定则是Object。
  • D getGenericDeclaration(): 返回的是声明这个Type所在类的Type
  • String getName() : 返回的是这个 type variable的名称
(4)、ParameterizedType

它标识参数化类型,源码如下:

/**
 * This interface represents a parameterized type such as {@code
 * 'Set<String>'}.
 *
 * @since 1.5
 */
public interface ParameterizedType extends Type {

    /**
     * Returns an array of the actual type arguments for this type.
     * <p>
     * If this type models a non parameterized type nested within a
     * parameterized type, this method returns a zero length array. The generic
     * type of the following {@code field} declaration is an example for a
     * parameterized type without type arguments.
     *
     * <pre>
     * A<String>.B field;
     *
     * class A<T> {
     *     class B {
     *     }
     * }</pre>
     *
     *
     * @return the actual type arguments
     *
     * @throws TypeNotPresentException
     *             if one of the type arguments cannot be found
     * @throws MalformedParameterizedTypeException
     *             if one of the type arguments cannot be instantiated for some
     *             reason
     */
     //返回 这个 Type 类型的参数的实际类型数组。
     // 如 Map<String,Person> map 
     //这个 ParameterizedType 返回的是 String 类,
     //Person 类的全限定类名的 Type Array。
    Type[] getActualTypeArguments();

    /**
     * Returns the parent / owner type, if this type is an inner type, otherwise
     * {@code null} is returned if this is a top-level type.
     *
     * @return the owner type or {@code null} if this is a top-level type
     *
     * @throws TypeNotPresentException
     *             if one of the type arguments cannot be found
     * @throws MalformedParameterizedTypeException
     *             if the owner type cannot be instantiated for some reason
     */
    Type getOwnerType();

    /**
     * Returns the declaring type of this parameterized type.
     * <p>
     * The raw type of {@code Set<String> field;} is {@code Set}.
     *
     * @return the raw type of this parameterized type
     */
    //返回的是当前这个 ParameterizedType 的类型。 
    //如 Map<String,Person> map 
    //这个 ParameterizedType 返回的是 Map 类的全限定类名的 Type Array。
    Type getRawType();
}

官方给的解释是

ParameterizedType represents a parameterized type such as Collection<String>

需要注意的是,并不只是 Collection<String> 才是 parameterized,任何类似于 ClassName<V> 这样的类型都是 ParameterizedType 。比如下面的这些都是

  • Map<String, Person> map;
  • Set<String> set1;
  • Class<?> clz;
  • Holder<String> holder;
  • List<String> list;
  • static class Holder<V>{}

而类似于这样的 ClassName 不是 ParameterizedType.

Set mSet;
List mList;
(5)GenericArrayType
/**
 * {@code GenericArrayType} represents an array type whose component
 * type is either a parameterized type or a type variable.
 * @since 1.5
 */
public interface GenericArrayType extends Type {
    /**
     * Returns a {@code Type} object representing the component type
     * of this array. This method creates the component type of the
     * array.  See the declaration of {@link
     * java.lang.reflect.ParameterizedType ParameterizedType} for the
     * semantics of the creation process for parameterized types and
     * see {@link java.lang.reflect.TypeVariable TypeVariable} for the
     * creation process for type variables.
     *
     * @return  a {@code Type} object representing the component type
     *     of this array
     * @throws TypeNotPresentException if the underlying array type's
     *     component type refers to a non-existent type declaration
     * @throws MalformedParameterizedTypeException if  the
     *     underlying array type's component type refers to a
     *     parameterized type that cannot be instantiated for any reason
     */
    Type getGenericComponentType();
}

简单的来说就是:泛型数组,组成数组的元素中有泛型则实现了该接口;它的组成元素是ParameterizedType或者TypeVariable类型

// 属于 GenericArrayType
List<String>[] pTypeArray;
// 属于 GenericArrayType
T[] vTypeArray;
// 不属于 GenericArrayType
List<String> list;
// 不属于 GenericArrayType
String[] strings;
// 不属于 GenericArrayType
Person[] ints;

下面我们一起来看一下例子

public class GenericArrayTypeBean<T> {
    public void test(List<String>[] pTypeArray, T[] vTypeArray,
             List<String> list, String[] strings, Person[] ints) {
        }
}
 public static void testGenericArrayType() {
        Method method = GenericArrayTypeBean.class.getDeclaredMethods()[0];
        System.out.println(method);
        // public void test(List<String>[] pTypeArray, T[]
        // vTypeArray,List<String> list, String[] strings, Person[] ints)
        Type[] types = method.getGenericParameterTypes(); // 这是 Method 中的方法
        for (Type type : types) {
            System.out.println(type instanceof GenericArrayType);// 依次输出true,true,false,false,false
        }
    }

输出结果

public void com.demo.beans.GenericArrayTypeBean.test(java.util.List[],java.lang.Object[],java.util.List,java.lang.String[],com.demo.beans.Person[])
true
true
false
false
false
(6)、WildcardType 通配符的类型
/**
 * Represents a wildcard type argument.
 * Examples include:    <pre>
 * {@code <?>}
 * {@code <? extends E>}
 * {@code <? super T>}
 * </pre>
 * A wildcard type can have explicit <i>extends</i> bounds
 * or explicit <i>super</i> bounds or neither, but not both.
 *
 * @author Scott Seligman
 * @since 1.5
 */
public interface WildcardType extends Type {

    /**
     * Return the upper bounds of this wildcard type argument
     * as given by the <i>extends</i> clause.
     * Return an empty array if no such bounds are explicitly given.
     *
     * @return the extends bounds of this wildcard type argument
     */
    Type[] extendsBounds();

    /**
     * Return the lower bounds of this wildcard type argument
     * as given by the <i>super</i> clause.
     * Return an empty array if no such bounds are explicitly given.
     *
     * @return the super bounds of this wildcard type argument
     */

看类注释知道

{@code ?}, {@code ? extends Number}, or {@code ? super Integer} 这些类型 都属于 WildcardType PS:extends 用于指定上边界,没有指定的话上边界默认是Object,super用来指定下边界,没有指定的话为null

主要方法解释:

  • Type[] getLowerBounds() 得到上边界 Type 的数组
  • Type[] getUpperBounds() 得到下边界 Type 的数组
(7)、Type总结

Type及其子接口的来历 泛型出现之前的类型 没有泛型的时候,只有原始类型。此时,所有原始类型都通过字节码文件类Class进行抽象。Class类的一个具体对象就代表一个指定的原始类型。 泛型出现之后的类型 泛型出现之后,扩充了数据类型。从只有原始类型扩充了参数画类型、类型变量类型、限定符类型、泛型数组类型。

2、Generic Method Return Type(方法返回值类型的泛型)

如果你获得了java.lang.reflect.Method对象,你也是有可能获得它的返回值类型的泛型信息的。这不会是任何参数化类型的Method对象,除了在类里面使用了参数化类型。举一个类有参数化返回值类型的返回值:

public class MyClass {
  protected List<String> stringList = new ArrayList<String>();
  public List<String> getStringList(){
    return this.stringList;
  }
}

在这种情况下,可以取得getStringList()方法的泛型返回值类型。换句话说,是可以检测到getStringList()方法返回的是List<String> 类型而不仅仅是List。代码如下:

Method method = MyClass.class.getMethod("getStringList", null);
Type returnType = method.getGenericReturnType();
if(returnType instanceof ParameterizedType){
    ParameterizedType type = (ParameterizedType) returnType;
    Type[] typeArguments = type.getActualTypeArguments();
    for(Type typeArgument : typeArguments){
        Class typeArgClass = (Class) typeArgument;
        System.out.println("typeArgClass = " + typeArgClass);
    }
}

这段代码将会打印出“typeArgClass = java.lang.String”.Type[ ]类型的数组typeArguements中包含一个项,一个代表实现了Type接口的java.lang.String.Class的Class实例。

2、Generic Method Parameter Types

在运行时,你也可以通过Java反射机制访问泛型参数的类型,下面举例说明

public class MyClass {
  protected List<String> stringList = new ArrayList<String>();
  public void setStringList(List<String> list){
    this.stringList = list;
  }
}

你可以像这样来访问其方法参数的参数化类型:

method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
    if(genericParameterType instanceof ParameterizedType){
        ParameterizedType aType = (ParameterizedType) genericParameterType;
        Type[] parameterArgTypes = aType.getActualTypeArguments();
        for(Type parameterArgType : parameterArgTypes){
            Class parameterArgClass = (Class) parameterArgType;
            System.out.println("parameterArgClass = " + parameterArgClass);
        }
    }
}

这段代码将会打印出“parameterArgType = java.lang.String”。Type[ ]类型的数组parameterArgTypes中包含一个项,一个代表实现了Type接口的java.lang.String.Class的Class实例。

至此反射讲解完毕,最后奉上别人做的比较不错的思维导图

Java反射.png

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编程

Python函数基础

函数是一种设计工具,它能让程序员将复杂的系统分解为可管理的部件 函数用于将相关功能打包并参数化 在Python中可以创建4种函数 全局函数:定义在模块中 //...

21250
来自专栏大数据钻研

JavaScript 知识点整理

JavaScript是按照ECMAScript标准设计和实现的,后文说的JavaScript语法其实是ES5的标准的实现。 先说说有哪些基础语法? 最基础语法有...

29150
来自专栏jessetalks

Javascript基础回顾 之(三) 面向对象

Javascript中的对象 什么是对象   我们可以把Javascript中对象理解为一组无序的键值对,就好像C#中的Dictionary<string,O...

338110
来自专栏我的技术专栏

漫谈C++:良好的编程习惯与编程要点

13570
来自专栏大数据钻研

JavaScript 知识点整理

JavaScript是按照ECMAScript标准设计和实现的,后文说的JavaScript语法其实是ES5的标准的实现。 先说说有哪些基础语法? 最基础语法有...

22950
来自专栏WindCoder

Java基础小结(一)

1、default (即缺省,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。

12310
来自专栏GreenLeaves

JavaScript之面向对象的概念,对象属性和对象属性的特性简介

  一、大家都知道,面向对象语言有一个标志,那就是他们都有类的概念,通过类我们可以创建任意多个具有相同属性和方法的对象。但ECMAScript(指定JavaSc...

17660
来自专栏阿凯的Excel

Python读书笔记16(循环大法好!while少不了)

今天和大家分享一个新的循环语句while! 之前学过for循环语句用于遍历列表、元组、字典内的值,我们重温一下! ? 这种for循环语句是根据列表元素值的数量来...

38350
来自专栏我是攻城师

Java基础类String了解一下

当你路过一些商场或者地铁口的时候,有没有被千篇一律的"xx健身,了解一下" 所烦到。

11820
来自专栏cs

c++面向对象的一些问题1.0

构造函数 特殊的成员函数,给对象的初始化,不需要用户调用,建立对象时,自动执行 它的函数名字与类相同,可以重载,没有返回值和函数类型。 如果不写构造函...

305100

扫码关注云+社区

领取腾讯云代金券