深刻分析Java反射(一)-核心类库和方法

前提

Java反射的API在JavaSE1.7的时候已经基本完善,可是本文编写的时候使用的是Oracle JDK11,由于JDK11对于sun包下的源码也上传了,能够直接经过IDE查看对应的源码和进行Debug。html

本文主要介绍反射的基本概念以及核心类ClassConstructorMethodFieldParameter的经常使用方法。java

本文极长,请准备一个使本身舒服的姿式阅读。spring

什么是反射

反射(Reflection)是一种能够在运行时检查和动态调用类、构造、方法、属性等等的编程语言的能力,甚至能够不须要在编译期感知类的名称、方法的名称等等。Oracle关于Java反射的官方教程中指出反射是由应用程序使用,用于检查或修改在Java虚拟机中运行的应用程序的运行时行为,这是一个相对高级的功能,须要由掌握Java语言基础知识的开发者使用编程

反射的优势有不少,前面提到能够检查或修改应用程序的运行时行为、抑制修饰符限制直接访问私有属性等等,这里主要列举一下它的缺点:数组

  • 性能开销:因为反射涉及动态解析的类型,所以没法执行某些Java虚拟机优化。所以,反射操做的性能低于非反射操做,应避免在性能敏感应用程序中频繁调用反射操做代码片断。
  • 安全限制:反射须要运行时权限,不能在安全管理器(security manager)下进行反射操做。
  • 代码可移植性:反射代码打破了抽象,反射的类库有可能随着平台(JDK)升级发生改变,反射代码中容许执行非反射代码的逻辑例如容许访问私有字段,这些问题都有可能影响到代码的可移植性。

JDK中对和反射相关的类库集中在java.lang.reflect包和java.lang包中,java.lang.reflect包和java.lang包是开发者能够直接使用的,部分java.lang.reflect包中接口的实现类存放在sun.reflect包中,通常状况下sun包下的类库有可能跟随平台升级发生改变,通常尽可能少用,不然有可能由于JDK升级致使原来的代码没法正常运行。还有部分反射相关的类库存放在jdk.internal.reflect包中,这个包是JDK内部使用的包,通常也不建议滥用其中的类库。能够理解为java.lang.reflect包和java.lang包中的类库就是面向开发者的类库。安全

图解反射核心类的体系

java.lang.reflect包反射核心类有核心类ClassConstructorMethodFieldParameter,它们的基础体系以下:oracle

java.lang.Class类继承体系:框架

java.lang.reflect.Constructor类继承体系:编程语言

java.lang.reflect.Method类继承体系:函数

java.lang.reflect.Field类继承体系:

java.lang.reflect.Parameter类继承体系:

由它们的类继承图能够看出:

  • Class、Constructor、Method、Field、Parameter共有的父接口是AnnotatedElement。
  • Constructor、Method、Field共有的父类是AnnotatedElement、AccessibleObject和Member。
  • Constructor、Method共有的父类是AnnotatedElement、AccessibleObject、Member、GenericDeclaration和Executable。

下面会先简单分析AnnotatedElementAccessibleObjectMemberGenericDeclarationExecutable几个类提供的功能,而后重点分析ClassConstructorMethodFieldParameter的经常使用方法。

这里先说一个规律,在Class中,getXXX()方法和getDeclearedXXX()方法有所区别。注解类型Annotation的操做方法例外,由于基于注解的修饰符一定是public的:

  • getDeclaredMethod(s):返回类或接口声明的全部方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。对于获取Method对象,Method[] methods = clazz.getDeclaredMethods();返回的是clazz本类全部修饰符(public、default、private、protected)的方法数组,可是不包含继承而来的方法。
  • getMethod(s):返回某个类的全部公用(public)方法包括其继承类的公用方法,固然也包括它所实现接口的方法。对于获取Method对象,Method[] methods = clazz.getMethods();表示返回clazz的父类、父类接口、本类、本类接口中的所有修饰符为public的方法数组。
  • getDeclaredField(s)和getField(s)、getDeclaredConstructor(s)和getConstructor(s)同上。
  • getDeclaredAnnotation(s):返回直接存在于此元素上的全部注解,此方法将忽略继承的注解,准确来讲就是忽略@Inherited注解的做用。
  • getAnnotation(s):返回此元素上存在的全部注解,包括继承的全部注解。

若是想获取一个类的全部修饰符的方法,包括全部父类中的方法,那么建议递归调用getDeclaredMethods()(所谓递归调用就是一直追溯目标类的父类递归调用getDeclaredMethods()方法直到父类为Object类型,这个思路能够参考Spring框架中的相关工具类)。获取一个类的全部Field、Constructor也能够相似操做,能够参考或者直接使用Spring中的工具类ReflectionUtils的相关方法。@Inherited元注解是一个标记注解,@Inherited阐述了某个被标注的Annotation类型是能够被继承的,详细的在分析AnnotatedElement的时候再展开。

Type接口

java.lang.reflect.Type接口是Java中全部类型的共同父类,这些类型包括原始类型、泛型类型、数组类型、类型变量和基本类型,接口定义以下:

public interface Type {

    default String getTypeName() {
        return toString();
    }
}

AnnotatedElement接口

AnnotatedElement是一个接口,它定义的方法主要和注解操做相关,例如用于判断注解的存在性和获取注解等等。

方法 功能
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 判断指定的注解类型在当前的实例上是否存在
<T extends Annotation> T getAnnotation(Class<T> annotationClass) 获取当前实例上指定注解类型的注解实例,不存在时返回null
Annotation[] getAnnotations() 获取当前实例上全部注解实例,包括继承得到的注解,不存在则返回长度为0的数组
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) 获取当前实例上指定注解类型的注解实例,不包括继承得到的注解,不存在则返回长度为0的数组
<T extends Annotation> T[] getDeclaredAnnotations(Class<T> annotationClass) 获取当前实例上全部的注解实例,不包括继承得到的注解,不存在则返回长度为0的数组
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) 在不使用@Repeatable的时候,功能和getDeclaredAnnotations方法一致,若是使用了@Repeatable,则合并解析@Repeatable后的结果
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) 若是指定annotationClass注解类型可继承(使用了@Inherited),那么递归调用getDeclaredAnnotationsByType

举个简单例子:

public class Main {

    public static void main(String[] args) {
        Class<?> clazz = Sub.class;
        System.out.println("-----getAnnotations-----");
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation.toString());
        }
        System.out.println("-----getDeclaredAnnotation-->SupperAnnotation-----");
        SupperAnnotation declaredSupperAnnotation = clazz.getDeclaredAnnotation(SupperAnnotation.class);
        System.out.println(declaredSupperAnnotation);
        System.out.println("-----getAnnotation-->SupperAnnotation-----");
        SupperAnnotation supperAnnotation = clazz.getAnnotation(SupperAnnotation.class);
        System.out.println(supperAnnotation);
        System.out.println("-----getDeclaredAnnotation-->SubAnnotation-----");
        SubAnnotation declaredSubAnnotation = clazz.getDeclaredAnnotation(SubAnnotation.class);
        System.out.println(declaredSubAnnotation);
        System.out.println("-----getDeclaredAnnotationsByType-->SubAnnotation-----");
        SubAnnotation[] declaredSubAnnotationsByType = clazz.getDeclaredAnnotationsByType(SubAnnotation.class);
        for (SubAnnotation subAnnotation : declaredSubAnnotationsByType) {
            System.out.println(subAnnotation);
        }
        System.out.println("-----getDeclaredAnnotationsByType-->SupperAnnotation-----");
        SupperAnnotation[] declaredSupperAnnotationsByType = clazz.getDeclaredAnnotationsByType(SupperAnnotation.class);
        for (SupperAnnotation supperAnnotation1 : declaredSupperAnnotationsByType) {
            System.out.println(supperAnnotation1);
        }
        System.out.println("-----getAnnotationsByType-->SupperAnnotation-----");
        SupperAnnotation[] supperAnnotationsByType = clazz.getAnnotationsByType(SupperAnnotation.class);
        for (SupperAnnotation supperAnnotation2 : supperAnnotationsByType) {
            System.out.println(supperAnnotation2);
        }
    }


    @SupperAnnotation
    private static class Supper {

    }

    @SubAnnotation
    private static class Sub extends Supper {

    }

    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    @Target(ElementType.TYPE)
    private @interface SupperAnnotation {

        String value() default "SupperAnnotation";
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Target(ElementType.TYPE)
    private @interface SubAnnotation {

        String value() default "SubAnnotation";
    }
}

运行后输出:

-----getAnnotations-----
@org.throwable.inherited.Main$SupperAnnotation(value=SupperAnnotation)
@org.throwable.inherited.Main$SubAnnotation(value=SubAnnotation)
-----getDeclaredAnnotation-->SupperAnnotation-----
null
-----getAnnotation-->SupperAnnotation-----
@org.throwable.inherited.Main$SupperAnnotation(value=SupperAnnotation)
-----getDeclaredAnnotation-->SubAnnotation-----
@org.throwable.inherited.Main$SubAnnotation(value=SubAnnotation)
-----getDeclaredAnnotationsByType-->SubAnnotation-----
@org.throwable.inherited.Main$SubAnnotation(value=SubAnnotation)
-----getDeclaredAnnotationsByType-->SupperAnnotation-----
-----getAnnotationsByType-->SupperAnnotation-----
@org.throwable.inherited.Main$SupperAnnotation(value=SupperAnnotation)

能够尝试注释掉@Inherited再运行一次,对比一下结果。若是注释掉@Inherited,从Sub这个类永远没法获取到它的父类Supper中的@SupperAnnotation。Class、Constructor、Method、Field、Parameter都实现了AnnotatedElement接口,因此它们都具有操做注解的功能。

Member接口

Member接口注解提供成员属性的一些描述,主要提供的方法以下:

方法 功能
Class<?> getDeclaringClass() 获取声明的Class对象,也就是获取当前Member实例的来源Class对象
String getName() 获取实例的名称,对于Constructor返回全类名,对于Method返回方法名,对于Field返回属性名
int getModifiers() 获取实例的修饰符
boolean isSynthetic() 是否合成的

这些方法里面除了isSynthetic()都比较好理解。synthetic总的来讲,是由编译器引入的字段、方法、类或其余结构,主要用于JVM内部使用,为了遵循某些规范而做的一些小技巧从而绕过这些规范,有点做弊的感受,只不过是由编译器光明正大为之,通常开发者是没有权限的(但事实上有时候仍是能被利用到的)。下面这个例子参考自synthetic Java合成类型:

public class Main {

    private static class Inner {
    }
    static void checkSynthetic (String name) {
        try {
            System.out.println (name + " : " + Class.forName (name).isSynthetic ());
        } catch (ClassNotFoundException exc) {
            exc.printStackTrace (System.out);
        }
    }
    public static void main(String[] args) throws Exception
    {
        new Inner ();
        checkSynthetic ("com.fcc.test.Main");
        checkSynthetic ("com.fcc.test.Main$Inner");
        checkSynthetic ("com.fcc.test.Main$1");
    }
}
//打印结果:
com.fcc.test.Main : false
com.fcc.test.Main$Inner : false
com.fcc.test.Main$1 : true
//编译结果,生成三个class文件: Main.class/Main$Inner/Main$1.class
// $FF: synthetic class
class Main$1 {
}

Inner这个内部类是私有的,私有内部类。拥有内部类的类编译后内外部类二者没有关系,那么私有内部类编译后默认是没有对外构造器的(若是以上代码中在Inner手动给一个public的构造器,Main$1是不会出现的),可是咱们又知道,外部类是能够引用内部类的,那么编译后,又是两个毫无关系的类,一个类没对外构造器,但另外一个类确实是有对这个类的实例对象权限(这里就是重点,内部类哪怕没有public构造器,外部类都有实例化内部类对象的权限)的,这种状况下编译器就会生成一个合成类,也就是Main$1,一个什么也没有的空类(是的,什么也没有,连构造器都没有)。但到这里,仍然不明白其实现原理是怎么样的,原先觉得合成类是那个内部类的副本,外部类访问内部类,在编译器认为只是和合成类交互,只是合成类只有外部类有权限访问,可是事实上,无论内部类怎么变化,合成类只是一个空的类,有点相似标记做用(真正做用倒是不得而知)。

AccessibleObject类

AccessibleObject是一个普通Java类,实现了AnnotatedElement接口,可是对应AnnotatedElement的非默认方法的实现都是直接抛异常,也就是AnnotatedElement的接口方法必须由AccessibleObject的子类去实现,我的认为AccessibleObject应该设计为抽象类。AccessibleObject在JDK1.1的时候已经存在,在JDK9的时候被改进过,添加了一些新的方法,下面列举一下经常使用的方法:

方法 功能
void setAccessible(boolean flag) 设置实例是否能够访问,若是设置为true,能够抑制修饰符,直接进行访问
boolean isAccessible() 返回实例是否能够访问,实际上这个值并不许确,它只有在setAccessible被调用的时候才会更新
boolean trySetAccessible() 功能相似于setAccessible(boolean flag),返回值决定是否抑制修饰符成功
static void setAccessible(AccessibleObject[] array, boolean flag) setAccessible(boolean flag)的批量操做方法

通常而言,咱们须要经过getModifiers()方法判断修饰符是否public,若是是非public,则须要调用setAccessible(true)进行修饰符抑制,不然会由于无权限访问会抛出异常。

GenericDeclaration接口

GenericDeclaration接口继承自AnnotatedElement,它的源码以下:

public interface GenericDeclaration extends AnnotatedElement {

    public TypeVariable<?>[] getTypeParameters();
}

新增了一个方法getTypeParameters()用于返回类型变量TypeVariable数组,这里的TypeVariable是类型变量,它的定义以下:

public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
    //得到泛型的类型(Type)上限数组,若未明确声明上边界则默认为Object
    Type[] getBounds();
    //获取声明该类型变量实体(即得到类、方法或构造器名)
    D getGenericDeclaration();
    //得到泛型参数的字面量名称,即K、V、E之类名称
    String getName();
    //得到泛型的注解类型(AnnotatedType)上限数组,若未明确声明上则为长度为0的空数组
    AnnotatedType[] getAnnotatedBounds();
}

后面的文章介绍泛型的时候再展开。

Executable类

Executable是一个抽象类,它继承自AccessibleObject,实现了MemberGenericDeclaration接口。Executable的实现类是MethodConstructor,它的主要功能是从MethodConstructor抽取出二者能够共用的一些方法例如注解的操做,参数的操做等等,这里不详细展开。

Modifier

Modifier主要提供一系列的静态方法,用于判断基于int类型的修饰符参数的具体类型,这个修饰符参数来源于Class、Constructor、Method、Field、Parameter的getModifiers()方法。下面介绍一下Modifier的主要方法:

方法 功能
static boolean isAbstract(int mod) 整数modifier参数是否包括abstract修饰符
static boolean isFinal(int mod) 整数modifier参数是否包括final修饰符
static boolean isInterface(int mod) 整数modifier参数是否包括interface修饰符
static boolean isNative(int mod) 整数modifier参数是否包括native修饰符
static boolean isPrivate(int mod) 整数modifier参数是否包括private修饰符
static boolean isProtected(int mod) 整数modifier参数是否包括protected修饰符
static boolean isPublic(int mod) 整数modifier参数是否包括public修饰符
static boolean isStatic(int mod) 整数modifier参数是否包括static修饰符
static boolean isStrict(int mod) 整数modifier参数是否包括strictfp修饰符
static boolean isSynchronized(int mod) 整数modifier参数是否包括synchronized修饰符
static boolean isTransient(int mod) 整数modifier参数是否包括transient修饰符
static boolean isVolatile(int mod) 整数modifier参数是否包括volatile修饰符
static boolean toString(int mod) 返回描述指定修饰符中的访问修饰符标志的字符串

Class类

Class实现了SerializableGenericDeclarationTypeAnnotatedElement接口,它提供了类型判断、类型实例化、获取方法列表、获取字段列表、获取父类泛型类型等方法。下面主要介绍一下它的主要方法:

方法 功能
Class<?> forName(String className) 传入全类名建立Class实例
T newInstance() 经过当前的Class实例进行实例化对象,返回的就是新建的对象
int getModifiers() native方法,返回当前Class的修饰符
String getName() 返回类名称,虚拟机中类名表示
String getCanonicalName() 返回类名称,便于理解的类名表示
String getSimpleName() 返回类名称,源代码中给出的底层类的简单名称
Package getPackage() 返回类的包属性
String getPackageName() 返回类的包路径名称
String toGenericString() 返回描述此Class的字符串,其中包括类型参数的字面量
TypeVariable<Class<T>>[] getTypeParameters() 获取类定义泛型的类型变量
Class<?>[] getClasses() 获取全部的修饰符为public的成员Class,包括父类
Class<?>[] getDeclaredClasses() 获取本类全部修饰符的成员Class,不包括父类
Constructor<?>[] getConstructors() 获取全部的修饰符为public的构造器,包括父类
Constructor<T> getConstructor(Class<?>... parameterTypes) 获取参数类型匹配的修饰符为public的构造器,包括父类
Constructor<?>[] getDeclaredConstructors() 获取本类全部修饰符的构造器,不包括父类
Constructor<T>[] getDeclaredConstructor(Class<?>... parameterTypes) 获取本类参数类型匹配的全部修饰符的构造器,不包括父类
Method[] getMethods() 获取本类全部的修饰符为public的方法列表,包括父类
Method[] getDeclaredMethods() 获取本类全部修饰符的方法列表,不包括父类
Method getMethod(String name, Class<?>... parameterTypes) 经过指定方法名和参数类型获取本类修饰符为public的方法,包括父类
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 经过指定方法名和参数类型获取本类不限修饰符的方法,不包括父类
Field[] getFields() 获取本类全部的修饰符为public的属性列表,包括父类
Field[] getDeclaredFields() 获取本类全部修饰符的属性列表,不包括父类
Field getField(String name) 经过指定属性名名获取本类修饰符为public的属性,包括父类
Field getDeclaredField(String name) 经过指定属性名获取本类不限修饰符的属性,不包括父类
Class<?>[] getInterfaces() 获取类实现的全部接口的Class数组
Type[] getGenericInterfaces() 获取类实现的全部泛型参数接口的Type数组
Class<? super T> getSuperclass() 获取当前类的父类的Class,若是当前类是Object、接口、基本数据类型(primitive)或者void,则返回null
Type getGenericSuperclass() 获取当前类的泛型参数父类的Type,若是当前类是Object、接口、基本数据类型(primitive)或者void,则返回null
native boolean isInstance(Object obj) 判断传入的object是否当前类的实例
native boolean isAssignableFrom(Class<?> cls) 判断传入的Class对象是否和当前类相同,或者是否当前类的超类或超接口
native boolean isInterface() 判断当前类是否接口
native boolean isArray() 判断当前类是否数组
native boolean isPrimitive() 判断当前类是否基本数据类型
boolean isAnnotation() 判断当前类是否注解类型
boolean isSynthetic() 判断当前类是否复合
native Class<?> getComponentType() 若是当前类是数组,返回数组元素的类型
Class<?> getEnclosingClass() 返回一个类,当前类(通常是成员类)在这个类(封闭类,相对于内部类的外部类或者说外面一层)中定义
Constructor<?> getEnclosingConstructor() 返回构造器,当前类是在这个构造函数中定义
Method getEnclosingMethod() 返回方法,当前类是在这个方法中定义
Module getModule() 返回模块,JDK9新增方法

getName()getCanonicalName()getSimpleName()都是用于获取类的名称,可是有所区别,下面举个列子说明一下:

public class Main {

	public static void main(String[] args) {
		Supper<String, List<Integer>> supper = new Supper<>();
		Class<?> clazz = supper.getClass();
		System.out.println("name->" + clazz.getName());
		System.out.println("canonicalName->" + clazz.getCanonicalName());
		System.out.println("simpleName->" + clazz.getSimpleName());
		System.out.println("======================================");
		String[][] strings = new String[1][1];
		System.out.println("name->" + strings.getClass().getName());
		System.out.println("canonicalName->" + strings.getClass().getCanonicalName());
		System.out.println("simpleName->" + strings.getClass().getSimpleName());
	}

	private static class Supper<K, V> {
		private K key;
		private V value;
        //省略setter和getter方法
	}
}

运行后输出结果:

name->club.throwable.reflect.Main$Supper
canonicalName->club.throwable.reflect.Main.Supper
simpleName->Supper
======================================
name->[[Ljava.lang.String;
canonicalName->java.lang.String[][]
simpleName->String[][]

简单理解为:

  • getName():用于获取类在Java虚拟机中的类名表示。
  • getCanonicalName():用于获取全类名,包括包路径,包路径以点号分隔。
  • getSimpleName():用于获取类名,不包括包路径。

下面再举一个例子经过类名进行实例化对象和操做,从例子能够看到,实例化对象能够不依赖new关键字,这就是反射的强大之处:

public class Main3 {

	public static void main(String[] args) throws Exception {
		Class<?> clazz = Class.forName("club.throwable.reflect.Main3$Supper");
		Supper supper = (Supper) clazz.newInstance();
		System.out.println(supper.sayHello("throwable"));
	}

	public static class Supper {

		public String sayHello(String name) {
			return String.format("%s say hello!", name);
		}
	}
}

这里须要注意一点,Class.forName方法只能使用在修饰符为public的类上,若是使用在其余修饰符类上会抛出异常(IllegalAccessException),那么,若是上面的Supper类的修饰符修改成private,怎么样才能正常实例化它?这个问题将会在下面分析Constructor的时候获得解决。另外,这里的Class.forName方法不是获取Class实例的惟一方式,总结有如下三种方式:

  • 一、使用类的字面量"类名.class"。类字面常量使得建立Class对象的引用时不会自动地初始化该对象,而是按照以前提到的加载,连接,初始化三个步骤,这三个步骤是个懒加载的过程,不使用的时候就不加载。
  • 二、使用Class.forName(全类名);方法。
  • 三、使用实例的getClass()方法。getClass()是全部的对象都可以使用的方法,由于getClass()方法是Object类的方法,全部的类都继承了Object,所以全部类的对象也都具备getClass()方法。

通常来讲,使用"类名.class",这样作即简单安全又比较高效。由于在编译时就会受到检查,所以不须要置于try语句块中,而且它根除了对forName()方法的调用(forName()方法是一个耗时比较多的方法),因此相对比较高效。

最后,分析一下这几个比较难懂的方法getEnclosingClass()getEnclosingConstructor()getEnclosingMethod()

  • getEnclosingClass():返回一个类,当前类(通常是成员类)在这个类(通常叫封闭类,相对于内部类的外部类或者说外面一层)中定义。
  • getEnclosingConstructor():返回构造器,当前类是在这个构造函数中定义。
  • getEnclosingClass():返回方法,当前类是在这个方法中定义。

咱们在新建一个类的时候,这个类可使另外一个类中定义的成员类、构造方法中定义的内部类、方法中定义的内部类。能够经过当前的类反向获取定义当前的类的类、构造或者方法,这三种状况对应上面三个方法。举个例子:

getEnclosingClass()方法使用例子:

public class Main5 {

    public static void main(String[] args) throws Exception{
        Class<Outter.Inner> clazz = Outter.Inner.class;
        Class<?> enclosingClass = clazz.getEnclosingClass();
        System.out.println(enclosingClass.getName());
    }
    // Inner类是Outter类的成员类
    public static class Outter {

        public static class Inner {

        }
    }
}

输出结果:

org.throwable.inherited.Main5$Outter

在这里,Inner就是当前定义的类,它是Outter的静态成员类,或者说Outter是Inner的封闭类,经过Inner的Class的getEnclosingClass()方法获取到的就是Outter的Class实例。

getEnclosingConstructor()方法使用例子:

public class Main6 {

    public static void main(String[] args) throws Exception {
        Outter outter = new Outter();
    }

    public static class Outter {

        //Outter的无参数构造器
        public Outter() {
            //构造中定义的内部类
            class Inner {

            }

            Class<Inner> innerClass = Inner.class;
            Class<?> enclosingClass = innerClass.getEnclosingClass();
            System.out.println(enclosingClass.getName());
            Constructor<?> enclosingConstructor = innerClass.getEnclosingConstructor();
            System.out.println(enclosingConstructor.getName());
        }
    }
}

输出结果:

org.throwable.inherited.Main6$Outter
org.throwable.inherited.Main6$Outter

在这里,Inner是Outter的无参数构造里面定义的构造内部类,它也只能在Outter的无参数构造里面使用,经过Inner的Class的getEnclosingConstructor()方法获取到的就是Outter的无参数构造。

getEnclosingMethod()方法使用例子:

public class Main7 {

    public static void main(String[] args) throws Exception {
        Outter outter = new Outter();
        outter.print();
    }

    public static class Outter {

        public void print(){
            //方法print中定义的内部类
            class Inner {

            }

            Class<Inner> innerClass = Inner.class;
            Class<?> enclosingClass = innerClass.getEnclosingClass();
            System.out.println(enclosingClass.getName());
            Method enclosingMethod = innerClass.getEnclosingMethod();
            System.out.println(enclosingMethod.getName());
        }
    }
}

输出结果:

org.throwable.inherited.Main7$Outter
print

在这里,Inner是Outter的print方法里面定义的方法内部类,它也只能在Outter的print方法里面使用,经过Inner的Class的getEnclosingMethod()方法获取到的就是Outter的print方法。这种方式可能不经常使用,可是能够在某版本的spring-jdbc的JdbcTemplate的源码中看到相似的类定义逻辑。

前面介绍过getXXX()方法和getDeclearedXXX()方法有所区别,这里作个对比表格:

Class中获取Field列表的方法:

Class中的API 获取全部的Field 包括继承的Field 包括私有的Field
getDeclaredField() N N Y
getField() N Y N
getDeclaredFields() Y N Y
getFields() Y Y N

Class中获取Method列表的方法:

Class中的API 获取全部的Method 包括继承的Method 包括私有的Method
getDeclaredMethod() N N Y
getMethod() N Y N
getDeclaredMethods() Y N Y
getMethods() Y Y N

Class中获取Constructor列表的方法:

Class中的API 获取全部的Constructor 包括私有的Constructor
getDeclaredConstructor() N Y
getConstructor() N N
getDeclaredConstructors() Y Y
getConstructors() Y N

Constructor类

Constructor用于描述一个类的构造函数。它除了能获取到构造的注解信息、参数的注解信息、参数的信息以外,还有一个很重要的做用是能够抑制修饰符进行实例化,而Class的实例化方法newInstance只能实例化修饰符为public的类。Constructor的主要方法以下:

方法 功能
Class<T> getDeclaringClass() 获取当前构造的定义类
String getName() 获取当前构造的名称
int getModifiers() 获取当前构造的修饰符
String toGenericString() 返回描述此构造的字符串,其中包括类型参数的字面量
TypeVariable<Constructor<T>>[] getTypeParameters() 获取类定义泛型参数的类型变量
Class<?>[] getExceptionTypes() 获取当前构造异常类型数组,若是不存在则返回一个长度为0的数组
Type[] getGenericExceptionTypes() 获取当前构造异常类型数组的泛型类型,若是不存在则返回一个长度为0的数组
Type[] getGenericParameterTypes() 获取当前构造参数的泛型类型,若是不存在则返回一个长度为0的数组
Annotation[][] getParameterAnnotations() 获取当前构造参数的注解数组,这里是二维数组的缘由是一个参数可使用多个注解
int getParameterCount() 获取当前构造参数的数量
Class<?>[] getParameterTypes() 获取当前构造参数的Class数组
boolean isSynthetic() 当前构造是否复合的
boolean isVarArgs() 当前构造是否使用不定参数
T newInstance(Object...initargs) 使用此构造对象表示的构造方法来建立该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例
Parameter[] getParameters() 返回此构造对象的参数Parameter数组,若是没有则返回一个长度为0的数组
void setAccessible(boolean flag) 抑制构造访问修饰符的权限判断

下面咱们举个例子说明使用构造实例化对象能够抑制修饰符访问权限控制的问题:

public class Main8 {

    public static void main(String[] args) throws Exception{
        Class<Supper> supperClass = Supper.class;
        Constructor<Supper> constructor = supperClass.getDeclaredConstructor();
        constructor.setAccessible(Boolean.TRUE);
        Supper supper = constructor.newInstance();
        supper.sayHello("throwable");
    }

    private static class Supper {

        public void sayHello(String name) {
            System.out.println(String.format("%s say hello!", name));
        }
    }
}

输出结果:

throwable say hello!

这就是为何一些IOC容器的实现框架中实例化类的时候优先依赖于无参数构造的缘由,若是使用Class#newInstance方法,上面的代码调用逻辑会抛异常。

Method类

Method用于描述一个类的方法。它除了能获取方法的注解信息,还能获取方法参数、返回值的注解信息和其余信息。Method经常使用的方法以下:

方法 功能
Class<?> getDeclaringClass() 获取方法对应的Class
Object getDefaultValue() 获取方法上的注解成员的默认值
Class<?>[] getExceptionTypes() 获取方法上的异常类型数组,若是没有则返回一个长度为0的数组
Type[] getGenericExceptionTypes() 获取方法上的异常泛型类型Type数组,若是没有则返回一个长度为0的数组
Parameter[] getParameters() 返回方法的参数Parameter数组,若是没有则返回一个长度为0的数组
int getParameterCount() 返回方法的参数的数量
Class<?>[] getParameterTypes() 返回方法的参数的类型Class数组,若是没有则返回一个长度为0的数组
Annotation[][] getParameterAnnotations() 返回方法的注解Annotation数组,这里使用二维数组的缘由是一个参数可使用多个注解
TypeVariable<Method>[] getTypeParameters() 返回方法的泛型参数的类型变量
Type[] getGenericParameterTypes() 返回方法参数的泛型类型Type数组
Class<?> getReturnType() 返回方法的返回值的类型Class
Type getGenericReturnType() 返回方法的返回值的泛型类型Type
AnnotatedType getAnnotatedReturnType() 获取方法返回值的注解类型实例AnnotatedType
boolean isBridge() 是否桥方法
boolean isDefault() 是否接口的默认方法
boolean isSynthetic() 是否复合的
boolean isVarArgs() 是否使用了不定参数
String toGenericString() 返回方法带有泛型字面量的描述字符串
String getName() 返回方法的名称
int getModifiers() 返回方法的修饰符
Object invoke(Object obj, Object... args) 对带有指定参数的指定对象调用由此方法对象表示的底层方法
void setAccessible(boolean flag) 抑制方法访问修饰符的权限判断

关注其中的invoke(Object obj, Object... args)方法,第一个是要调用这个方法的对象,剩下的方法的参数,返回值就是该方法执行的返回值。若是方法的修饰符不是public,在调用invoke方法前须要调用setAccessible(boolean flag)抑制方法访问修饰符的权限判断,不然会抛出异常。举个例子以下:

public class Main10 {

    public static void main(String[] args) throws Exception{
        Class<Supper> supperClass = Supper.class;
        Supper supper = supperClass.newInstance();
        Method sayHello = supperClass.getDeclaredMethod("sayHello", String.class);
        sayHello.setAccessible(Boolean.TRUE);
        sayHello.invoke(supper,"throwable");
    }

    public static class Supper{

        private void sayHello(String name){
            System.out.println(String.format("%s say hello!", name));
        }
    }
}

输出结果:

throwable say hello!

Field类

Field类用来描述一个类里面的属性或者叫成员变量,经过Field能够获取属性的注解信息、泛型信息,获取和设置属性的值等等。Field的主要方法以下:

方法 功能
String getName() 返回该属性的名称
int getModifiers() 返回该属性的修饰符
Class<?> getType() 返回该属性的类型Class
Class<?> getParameterizedType() 返回该属性的泛型类型Type
boolean isSynthetic() 该属性是否复合的
boolean isEnumConstant() 该属性是否枚举类型的元素
Object get(Object obj) 经过对象实例获取该属性的值
void set(Object obj,Object value) 经过对象实例设置该属性的值
void setAccessible(boolean flag) 抑制属性访问修饰符的权限判断

这里忽略了注解以及Field实现了FieldAccessor接口中的getBooleansetBoolean等方法。下面举个例子说明一下Field的用法:

public class Main12 {

    public static void main(String[] args) throws Exception {
        Class<Supper> supperClass = Supper.class;
        Supper supper = supperClass.newInstance();
        Method sayHello = supperClass.getDeclaredMethod("sayHello");
        sayHello.setAccessible(Boolean.TRUE);
        Field name = supperClass.getDeclaredField("name");
        name.setAccessible(Boolean.TRUE);
        name.set(supper,"throwable");
        System.out.println("Field get-->" + name.get(supper));
        sayHello.invoke(supper);
        name.set(supper, "throwable-10086");
        System.out.println("Field get-->" + name.get(supper));
        sayHello.invoke(supper);
    }

    public static class Supper {

        private String name;

        private void sayHello() {
            System.out.println(String.format("%s say hello!", name));
        }
    }
}

输出结果:

Field get-->throwable
throwable say hello!
Field get-->throwable-10086
throwable-10086 say hello!

Parameter类

Parameter用于描述Method或者Constructor的参数,主要是用于获取参数的名称。由于在Java中没有形式参数的概念,也就是参数都是没有名称的。Jdk1.8新增了Parameter用来填补这个问题,使用javac编译器的时候加上-parameters参数的话,会在生成的.class文件中额外存储参数的元信息,这样会致使.class文件的大小增长。当你输入javac -help的时候,你会看到-parameters这个选项。获取Parameter的方法是Method或者Constructor的父类Executable的getParamaters方法。通常而言,Parameter是用于获取参数名称的后备方案,由于Jdk1.8以前没有这个类,而且即便使用了Jdk1.8若是javac编译器的时候没有加上-parameters参数的话,经过Parameter获取到的参数名称将会是"arg0"、"arg1"..."argn"相似的没有意义的参数名称。通常框架中使用其余方法解析方法或者构造器的参数名称,参考Spring的源码,具体是LocalVariableTableParameterNameDiscoverer,是使用ASM去解析和读取类文件字节码,提取参数名称。Parameter的主要方法以下:

方法 功能
String getName() 返回该参数的名称
int getModifiers() 返回该参数的修饰符
Class<?> getType() 返回该参数的类型Class
Class<?> getParameterizedType() 返回该参数的泛型类型Type
boolean isNamePresent() 该参数的名称是否保存在class文件中,须要编译时加参数-parameters
boolean isImplicit() 该参数是否隐式声明
boolean isSynthetic() 该参数是否复合的
boolean isVarArgs() 该参数是否不定参数

这里举个例子,编译时候添加参数-parameters

public class Main11 {

    public static void main(String[] args) throws Exception {
        Class<Supper> supperClass = Supper.class;
        Method sayHello = supperClass.getDeclaredMethod("sayHello", String.class);
        sayHello.setAccessible(Boolean.TRUE);
        Parameter[] parameters = sayHello.getParameters();
        for (Parameter parameter : parameters) {
            System.out.println("isNamePresent->" + parameter.isNamePresent());
            System.out.println("isImplicit->" + parameter.isImplicit());
            System.out.println("getName->" + parameter.getName());
            System.out.println("=====================");
        }

    }

    public static class Supper {

        private void sayHello(String name) {
            System.out.println(String.format("%s say hello!", name));
        }
    }
}

输出结果:

isNamePresent->true
isImplicit->false
getName->name
=====================

若是不设置编译参数-parameters,会输出下面的结果:

isNamePresent->false
isImplicit->false
getName->arg0
=====================

小结

这篇文章开篇对反射的基本进行介绍,后面花大量篇幅列举了相关类库的API和API使用,掌握这些类库,才能轻松地进行反射编程。

我的博客

(本文完 e-a-2018122)

原文出处:https://www.cnblogs.com/throwable/p/12272229.html

相关文章
相关标签/搜索