1. 程式人生 > >深入分析Java反射(一)-核心類庫和方法

深入分析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使用,掌握這些類庫,才能輕鬆地進行反射程式設計。

個人部落格

  • Throwable's Blog

(本文完 e-a-20181