深刻分析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

相關文章
相關標籤/搜索