深入分析Java反射(一)-核心類庫和方法


前提

Java反射的API在JavaSE1.7的時候已經基本完善,但是本文編寫的時候使用的是Oracle JDK11,因為JDK11對於sun包下的源碼也上傳了,可以直接通過IDE查看對應的源碼和進行Debug。

本文主要介紹反射的基本概念以及核心類ClassConstructorMethodFieldParameter的常用方法。

本文極長,請准備一個使自己舒服的姿勢閱讀。

什么是反射

反射(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,它們的基礎體系如下:

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實例的唯一方式,總結有以下三種方式:

  • 1、使用類的字面量"類名.class"。類字面常量使得創建Class對象的引用時不會自動地初始化該對象,而是按照之前提到的加載,鏈接,初始化三個步驟,這三個步驟是個懶加載的過程,不使用的時候就不加載。
  • 2、使用Class.forName(全類名);方法。
  • 3、使用實例的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)

技術公眾號(《Throwable文摘》),不定期推送筆者原創技術文章(絕不抄襲或者轉載):

娛樂公眾號(《天天沙雕》),甄選奇趣沙雕圖文和視頻不定期推送,緩解生活工作壓力:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM