Java反射,看完就會用

Java備忘錄發表於2023-12-29

什麼是反射

在說反射概念之前,我們先說另外2個概念:編譯期執行期

編譯期:

  • 編譯期是原始碼從文字形式轉換為位元組碼的過程,這發生在Java程式碼被JVM執行之前。
  • 在編譯期,編譯器對原始碼進行語法檢查、型別檢查、變數名解析等操作,確保程式碼符合Java的語法規則,並將其編譯成位元組碼(.class檔案)。
  • 編譯期間的操作基於靜態型別資訊。編譯器只能使用它在編譯時瞭解的資訊,而不能知曉執行時的具體情況。

執行期:

  • 執行期是指編譯後的程式碼在Java虛擬機器(JVM)上執行的過程。
  • 在執行期,JVM執行編譯後的位元組碼,並進行各種執行時操作,如記憶體分配、垃圾回收等。

反射機制主要發生在執行期。反射允許程式在執行時動態訪問和操作類、物件、方法、屬性等。

使用反射,程式可以獲取類的資訊(如類名、方法、欄位等),並可以建立物件、呼叫方法、修改欄位值,這些都是在執行時發生的。

補充:在學習JVM記憶體結構時,我們知道在類資訊是存放在方法區的。

反射的優缺點

優點:

在執行時獲得類的各種內容,進行反編譯,對於Java這種先編譯再執行的語言,能夠讓我們很方便的建立靈活的程式碼,這些程式碼可以在執行時裝配,無需在元件之間進行原始碼的連結,更加容易實現物件導向。

缺點:

(1)反射會消耗一定的系統資源,因此,如果不需要動態地建立一個物件,那麼就不需要用反射;

(2)反射呼叫方法時可以忽略許可權檢查,因此可能會破壞封裝性而導致安全問題。

反射的入口:Class類

類 Class 的例項代表在執行中的 Java 應用程式中的類和介面。列舉是一種類,註解是一種介面。每個陣列也屬於一個類,這個類以 Class 物件的形式反映出來,這個 Class 物件被所有具有相同元素型別和維數的陣列共享。Java 的基本型別(boolean、byte、char、short、int、long、float 和 double)以及關鍵字 void 也表示為 Class 物件。

三種方式獲取Class物件

  • 透過例項物件獲取Class物件

  • 透過類名.class獲取Class物件

  • 透過Class.forName(String className)獲取Class物件

@SpringBootTest
class DemoReflectionApplicationTests {

    @Test
    void contextLoads() throws ClassNotFoundException {
        User user = new User();
        Class<? extends User> u1 = user.getClass();
        System.out.println(u1.getName());
        System.out.println(u1.hashCode());

        Class<User> u2 = User.class;
        System.out.println(u2.hashCode());

        Class<?> u3 = Class.forName("com.example.reflection.pojo.User");
        System.out.println(u3.hashCode());
    }

}
com.example.reflection.pojo.User
1970707120
1970707120
1970707120

在執行期間,一個類只有一個Class物件產生。

獲取到Class物件,我們能拿到哪些資訊?

獲取名稱資訊

// 返回Java內部使用的真正的名稱
public String getName();
// 返回的名稱不帶包資訊
public String getSimpleName();
// 返回的名稱更加友好
public String getCanonicalName();
// 返回包資訊
public String getPackage();
@Test
void contextLoad2() throws ClassNotFoundException {
    Class<?> u = Class.forName("com.example.reflection.pojo.User");
    System.out.println(u.getName());
    System.out.println(u.getSimpleName());
    System.out.println(u.getCanonicalName());
    System.out.println(u.getPackage());

    Class<String> s = String.class;
    System.out.println(s.getName());
    System.out.println(s.getSimpleName());
    System.out.println(s.getCanonicalName());
    System.out.println(s.getPackage());
}

結果:

com.example.reflection.pojo.User
User
com.example.reflection.pojo.User
package com.example.reflection.pojo
java.lang.String
String
java.lang.String
package java.lang, Java Platform API Specification, version 1.8

獲取欄位資訊

Class獲取欄位資訊的方法:

// 返回所有的public欄位,包括其父類的,如果沒有欄位,返回空陣列
public Field[] getFields()
// 返回本類宣告的所有欄位,包括非public的,但不包括父類的
public Field[] getDeclaredFields()
// 返回本類或父類中指定名稱的public欄位,找不到丟擲異常NoSuchFieldException
public Field getField(String name)
// 返回本類中宣告的指定名稱的欄位,找不到丟擲異常NoSuchFieldException
public Field getDeclaredField(String name)

獲取到Field以後,進一步獲取欄位資訊:

// 獲取欄位的名稱
public String getName()
// 判斷當前程式是否有該欄位的訪問許可權
public boolean isAccessible()
// flag設為true表示忽略Java的訪問檢查機制,以允許讀寫非public的欄位
public void setAccessible(boolean flag)
// 獲取指定物件obj中該欄位的值
public Object get(Object obj)
// 將指定物件obj中該欄位的值設為value
public void set(Object obj, Object value)

// 返回欄位的修飾符
public int getModifiers()
//返回欄位的型別
public Class<? > getType()
//查詢欄位的註解資訊,下一章介紹註解
public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
public Annotation[] getDeclaredAnnotations()

獲取方法資訊

類中定義的靜態方法例項方法都算在內,用類Method表示。

//返回所有的public方法,包括其父類的,如果沒有方法,返回空陣列
public Method[] getMethods()
//返回本類宣告的所有方法,包括非public的,但不包括父類的
public Method[] getDeclaredMethods()
//返回本類或父類中指定名稱和引數型別的public方法,
//找不到丟擲異常NoSuchMethodException
public Method getMethod(String name, Class<? >... parameterTypes)
//返回本類中宣告的指定名稱和引數型別的方法,找不到丟擲異常NoSuchMethodException
public Method getDeclaredMethod(String name, Class<? >... parameterTypes)

獲取到Method後,進一步獲取方法的詳細資訊:

//獲取方法的名稱
public String getName()
//flag設為true表示忽略Java的訪問檢查機制,以允許呼叫非public的方法
public void setAccessible(boolean flag)
//在指定物件obj上呼叫Method代表的方法,傳遞的引數列表為args
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException

建立物件和構造方法

Class直接建立物件,呼叫無參構造器。

public T newInstance()

Class獲取構造器:

//獲取所有的public構造方法,返回值可能為長度為0的空陣列
public Constructor<? >[] getConstructors()
//獲取所有的構造方法,包括非public的
public Constructor<? >[] getDeclaredConstructors()
//獲取指定引數型別的public構造方法,沒找到丟擲異常NoSuchMethodException
public Constructor<T> getConstructor(Class<? >... parameterTypes)
//獲取指定引數型別的構造方法,包括非public的,沒找到丟擲異常NoSuchMethodException
public Constructor<T> getDeclaredConstructor(Class<? >... parameterTypes)

獲取到構造器用構造器建立物件:

public T newInstance(Object ... initargs)

型別檢查和轉換

型別檢查:

/**
* Params:
* obj – the object to check
* Returns:
* true if obj is an instance of this class
*/
public native boolean isInstance(Object obj);

型別轉換:

// 將一個物件轉換為由此 Class 物件所代表的類或介面。
public T cast(Object obj) 

獲取Class的型別資訊

Class代表的型別既可以是普通的類,也可以是內部類,還可以是基本型別、陣列等,對於一個給定的Class物件,它到底是什麼型別呢?可以透過以下方法進行檢查:

public native boolean isArray()  //是否是陣列
public native boolean isPrimitive()  //是否是基本型別
public native boolean isInterface()  //是否是介面
public boolean isEnum()  //是否是列舉
public boolean isAnnotation()  //是否是註解
public boolean isAnonymousClass()  //是否是匿名內部類
public boolean isMemberClass()  //是否是成員類,成員類定義在方法外,不是匿名類
public boolean isLocalClass()  //是否是本地類,本地類定義在方法內,不是匿名類

獲取類的宣告資訊

//獲取修飾符,返回值可透過Modifier類進行解讀
public native int getModifiers()
//獲取父類,如果為Object,父類為null
public native Class<? super T> getSuperclass()
//對於類,為自己宣告實現的所有介面,對於介面,為直接擴充套件的介面,不包括透過父類繼承的
public native Class<? >[] getInterfaces();
//自己宣告的註解
public Annotation[] getDeclaredAnnotations()
//所有的註解,包括繼承得到的
public Annotation[] getAnnotations()
//獲取或檢查指定型別的註解,包括繼承得到的
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

對於反射這一塊內容的話,其實理解起來並不是很難,但確實是很重要,所以非常建議把程式碼全部敲一遍。

對於程式設計學習來說,Coding百遍,其義自見。

仍然是不變的硬道理。

以上就是關於反射的所有內容,感謝閱讀!


聯絡我:
https://haibin9527.gitee.io/about_me/