什麼是反射
在說反射概念之前,我們先說另外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百遍,其義自見。
仍然是不變的硬道理。
以上就是關於反射的所有內容,感謝閱讀!