開篇之前先來一道面試題:請問 Java 中的反射是什麼?有什麼作用和優勢?如何使用反射?
你們可以先嘗試回答回答
如果不會我們就開始下面的文章
引言
什麼是反射
Java反射(Reflection)是指在運作時擷取對象資訊(例如類名、方法、屬性等),并且可以動态操作該對象,而無需事先知道該對象的靜态類型。它允許程式在運作時檢查對象和類,并且可以調用對象的方法、擷取對象的屬性和構造函數,甚至可以動态建立新的對象和數組。
反射的作用
Java反射的作用是使得程式能夠在運作時動态地操作對象和類,而不需要事先知道這些資訊,這種能力為程式設計帶來了很大的靈活性。使用反射可以實作很多進階功能,比如動态代理、注解處理、JavaBean屬性的自動設定等等。但是,由于反射操作具有較高的開銷,是以在需要性能的場合應該謹慎使用。
背景知識
Java反射的基本原理是通過使用Java的反射API來擷取類的資訊。Java的反射API提供了一個Class類,它可以用來描述類的資訊,例如類名、構造函數、方法和字段等。通過使用Class類的方法,可以擷取一個類的所有資訊。
Java反射可以讓程式在運作時動态地擷取類的資訊并進行操作,這使得程式的靈活性更高。但是,由于反射會犧牲一定的性能,是以在性能要求較高的情況下,應該盡量避免使用反射。
講解Java反射的核心概念和API
Java反射的核心概念主要涉及到以下幾個類:Class、Constructor、Field 和 Method。以下是這些類的主要API及其用法:
- Class類
- 作用:表示正在運作的Java應用程式中的類和接口。
- 擷取Class對象的方法:
- Class.forName(String className):根據類名加載類并擷取Class對象。
- object.getClass():根據已知對象擷取Class對象。
- ClassName.class:直接擷取類的Class對象。
- Constructor類
- 作用:描述類的構造方法。
- 擷取構造方法對象的方法:
- Class.getConstructor(Class<?>... parameterTypes):擷取類的public構造方法。
- Class.getDeclaredConstructor(Class<?>... parameterTypes):擷取類的所有構造方法,包括private的。
- 建立對象:
- Constructor.newInstance(Object... initargs):使用構造方法建立新的對象執行個體。
- Field類
- 作用:描述類的字段(成員變量)。
- 擷取字段對象的方法:
- Class.getField(String name):擷取類的public字段。
- Class.getDeclaredField(String name):擷取類的所有字段,包括private的。
- 擷取和設定字段值:
- Field.get(Object obj):擷取對象的字段值。
- Field.set(Object obj, Object value):設定對象的字段值。
- Method類
- 作用:描述類的方法。
- 擷取方法對象的方法:
- Class.getMethod(String name, Class<?>... parameterTypes):擷取類的public方法。
- Class.getDeclaredMethod(String name, Class<?>... parameterTypes):擷取類的所有方法,包括private的。
- 調用方法:
- Method.invoke(Object obj, Object... args):調用方法并傳回結果。
注意:在使用getDeclaredConstructor、getDeclaredField和getDeclaredMethod時,你可能需要調用setAccessible(true)方法來通路private成員。
以上是Java反射中的核心API。在實際使用過程中,你可能還需要了解其他API,例如Class.getModifiers()、Class.getSuperclass()等。但這些API不屬于反射的核心功能,是以在此不詳細列舉。
api詳解
Class
Class 類是 Java 反射的核心類,它表示正在運作的 Java 應用程式中的類和接口。以下是 Class 類中的一些重要方法:
獲得Class對象:
// 獲得Class對象
// 1.通過類名.class
Class<?> cls1 = Person.class;
// 2.通過Class.forName()
Class<?> cls2 = Class.forName("Reflection.bean.Person");
// 3.通過對象.getClass()
Person person = new Person();
Class<?> cls3 = person.getClass();
// 4.通過類加載器
Class<?> cls4 =Thread.currentThread().getContextClassLoader().loadClass("Reflection.bean.Person");
複制代碼
- 擷取類的名稱:
- String getName():傳回類的全限定名(包含包名)。
- String getSimpleName():傳回類的簡單名(不包含包名)。
- String getCanonicalName():傳回類的規範名(類似于全限定名,但對于内部類和數組類有所不同)。
- 加載和擷取類:
- static Class<?> forName(String className):根據類名加載類并擷取其 Class 對象。
- static Class<?> forName(String name, boolean initialize, ClassLoader loader):根據類名、是否初始化以及類加載器加載類并擷取其 Class 對象。
- 擷取類的修飾符:
- int getModifiers():傳回類的修飾符,例如 public、private、abstract 等。
- Modifier這個類裡有判斷傳回數字是什麼類型的方法
- 擷取類的父類和接口:
- Class<?> getSuperclass():傳回類的父類。
- Class<?>[] getInterfaces():傳回類實作的接口。
- 擷取類的構造方法、字段和方法:
- Constructor<?>[] getConstructors():傳回類的 public 構造方法。
- Constructor<?>[] getDeclaredConstructors():傳回類的所有構造方法,包括 private 的。
- Field[] getFields():傳回類的 public 字段。
- Field[] getDeclaredFields():傳回類的所有字段,包括 private 的。
- Method[] getMethods():傳回類的 public 方法。
- Method[] getDeclaredMethods():傳回類的所有方法,包括 private 的。
- 擷取類的注解:
- Annotation[] getAnnotations():傳回類的注解。
- Annotation[] getDeclaredAnnotations():傳回類的所有注解,包括繼承自父類的。
- T getAnnotation(Class<T> annotationClass):傳回類的指定類型的注解。
- 建立類的執行個體:
- T newInstance():使用預設構造方法建立類的執行個體。在 Java 9 之後被棄用,建議使用 Constructor.newInstance()。
- 判斷類的關系:
- boolean isAssignableFrom(Class<?> cls):判斷目前類是否為參數類的超類或接口。
- boolean isInstance(Object obj):判斷指定對象是否為目前類的執行個體。
- boolean isInterface():判斷目前類是否為接口。
- boolean isPrimitive():判斷目前類是否為基本類型。
- boolean isArray():判斷目前類是否為數組。
Constructor
Constructor 類是 Java 反射中的一個重要類,用于表示類的構造方法。以下是 Constructor 類中的一些重要方法:
- 擷取所有的構造方法:使用 Class 類的 getConstructors() 方法擷取類的所有公共構造方法,或使用 getDeclaredConstructors() 方法擷取類的所有構造方法(包括私有、受保護和預設通路權限的構造方法)。 示例:
Class<?> clazz = SomeClass.class;
Constructor<?>[] constructors = clazz.getConstructors(); // 擷取所有公共構造方法
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); // 擷取所有構造方法(包括非公共構造方法)
複制代碼
- 擷取特定的構造方法:使用 Class 類的 getConstructor() 方法擷取類的特定公共構造方法,或使用 getDeclaredConstructor() 方法擷取類的特定構造方法(包括私有、受保護和預設通路權限的構造方法)。這些方法需要傳入一個表示構造方法參數類型的 Class 對象數組。
Class<?> clazz = SomeClass.class;
try {
Constructor<?> constructorWithNoArgs = clazz.getConstructor(); // 擷取無參數的公共構造方法
Constructor<?> constructorWithArgs = clazz.getConstructor(String.class, int.class); // 擷取帶有 String 和 int 參數的公共構造方法
Constructor<?> declaredConstructorWithNoArgs = clazz.getDeclaredConstructor(); // 擷取無參數的構造方法(包括非公共構造方法)
Constructor<?> declaredConstructorWithArgs = clazz.getDeclaredConstructor(String.class, int.class); // 擷取帶有 String 和 int 參數的構造方法(包括非公共構造方法)
} catch (NoSuchMethodException e) {
System.err.println("構造方法未找到: " + e.getMessage());
}
複制代碼
- 擷取構造方法的名稱和修飾符:
- String getName():傳回構造方法的名稱,即類名。
- int getModifiers():傳回構造方法的修飾符,例如 public、private、protected 等。
- 擷取構造方法的參數類型和異常類型:
- Class<?>[] getParameterTypes():傳回構造方法的參數類型。
- Type[] getGenericParameterTypes():傳回構造方法的泛型參數類型。
- Class<?>[] getExceptionTypes():傳回構造方法抛出的異常類型。
- Type[] getGenericExceptionTypes():傳回構造方法抛出的泛型異常類型。
- 擷取和設定構造方法的通路權限:
- boolean isAccessible():傳回構造方法的可通路狀态。
- void setAccessible(boolean flag):設定構造方法的可通路狀态。如果要通路 private 構造方法,需要将 flag 設定為 true。
- 建立類的執行個體:
- T newInstance(Object... initargs):使用構造方法建立類的執行個體。initargs 參數表示構造方法的參數值。
- 擷取構造方法的注解:
- Annotation[] getAnnotations():傳回構造方法的注解。
- Annotation[] getDeclaredAnnotations():傳回構造方法的所有注解。
- T getAnnotation(Class<T> annotationClass):傳回構造方法的指定類型的注解。
- Annotation[][] getParameterAnnotations():傳回構造方法參數的注解。
Field
Field 類是 Java 反射中的一個重要類,用于表示類的字段(成員變量)。以下是 Field 類中的一些重要方法:
- 擷取字段的名稱和修飾符:
- String getName():傳回字段的名稱。
- int getModifiers():傳回字段的修飾符,例如 public、private、protected、static 等。
- 擷取字段的類型:
- Class<?> getType():傳回字段的類型。
- Type getGenericType():傳回字段的泛型類型。
- 擷取和設定字段的值:
- Object get(Object obj):傳回指定對象上該字段的值。對于靜态字段,obj 參數可以為 null。
- void set(Object obj, Object value):為指定對象上的該字段設定值。對于靜态字段,obj 參數可以為 null。
- boolean getBoolean(Object obj)、byte getByte(Object obj)、char getChar(Object obj)、double getDouble(Object obj)、float getFloat(Object obj)、int getInt(Object obj)、long getLong(Object obj) 和 short getShort(Object obj):分别傳回指定對象上該字段的布爾值、位元組值、字元值、雙精度浮點值、單精度浮點值、整數值、長整數值和短整數值。對于靜态字段,obj 參數可以為 null。
- void setBoolean(Object obj, boolean z)、void setByte(Object obj, byte b)、void setChar(Object obj, char c)、void setDouble(Object obj, double d)、void setFloat(Object obj, float f)、void setInt(Object obj, int i)、void setLong(Object obj, long l) 和 void setShort(Object obj, short s):分别為指定對象上的該字段設定布爾值、位元組值、字元值、雙精度浮點值、單精度浮點值、整數值、長整數值和短整數值。對于靜态字段,obj 參數可以為 null。
- 擷取和設定字段的通路權限:
- boolean isAccessible():傳回字段的可通路狀态。
- void setAccessible(boolean flag):設定字段的可通路狀态。如果要通路 private 字段,需要将 flag 設定為 true。
- 擷取字段的注解:
- Annotation[] getAnnotations():傳回字段的注解。
- Annotation[] getDeclaredAnnotations():傳回字段的所有注解。
- T getAnnotation(Class<T> annotationClass):傳回字段的指定類型的注解。
Method
Method 類是 Java 反射中的一個重要類,用于表示類的方法。以下是 Method 類中的一些重要方法:
- 擷取方法的名稱和修飾符:
- String getName():傳回方法的名稱。
- int getModifiers():傳回方法的修飾符,例如 public、private、protected、static 等。
- 擷取方法的參數類型和傳回類型:
- Class<?>[] getParameterTypes():傳回方法的參數類型。
- Type[] getGenericParameterTypes():傳回方法的泛型參數類型。
- Class<?> getReturnType():傳回方法的傳回類型。
- Type getGenericReturnType():傳回方法的泛型傳回類型。
- 擷取方法的異常類型:
- Class<?>[] getExceptionTypes():傳回方法抛出的異常類型。
- Type[] getGenericExceptionTypes():傳回方法抛出的泛型異常類型。
- 調用方法:
- Object invoke(Object obj, Object... args):調用指定對象上的該方法。obj 參數表示方法所在的對象,args 參數表示方法的參數值。對于靜态方法,obj 參數可以為 null。
- 擷取和設定方法的通路權限:
- boolean isAccessible():傳回方法的可通路狀态。
- void setAccessible(boolean flag):設定方法的可通路狀态。如果要通路 private 方法,需要将 flag 設定為 true。
- 擷取方法的注解:
- Annotation[] getAnnotations():傳回方法的注解。
- Annotation[] getDeclaredAnnotations():傳回方法的所有注解。
- T getAnnotation(Class<T> annotationClass):傳回方法的指定類型的注解。
- Annotation[][] getParameterAnnotations():傳回方法參數的注解。
建立執行個體
- 使用預設構造方法建立執行個體:
// 加載類
Class<?> cls = Class.forName("Reflection.bean.Person");
// 擷取預設構造方法(無參構造方法)
Constructor<?> constructor = cls.getDeclaredConstructor();
// 設定構造方法的通路權限,如果構造方法是私有的,則需要設定為 true
constructor.setAccessible(true);
// 建立執行個體
Object obj = constructor.newInstance();
複制代碼
- 使用帶參數的構造方法建立執行個體:
// 加載類
Class<?> cls = Class.forName("Reflection.bean.Person");
// 擷取帶參數的構造方法
Constructor<?> constructor = cls.getDeclaredConstructor(String.class, int.class);
// 設定構造方法的通路權限,如果構造方法是私有的,則需要設定為 true
constructor.setAccessible(true);
// 建立執行個體,傳入構造方法的參數值
Object obj = constructor.newInstance("張三", 42);
複制代碼
Class 類:可以使用 Class 類的 newInstance 方法建立類的執行個體,但需要注意的是,這種方式僅适用于具有無參(預設)構造方法的類。在 Java 9 中,Class 類的 newInstance 方法已被棄用,推薦使用 Constructor 類的 newInstance 方法。
// 加載類 Class\<?> cls = Class.forName("Reflection.bean.Person"); // 使用無參構造方法建立執行個體 Object obj = cls.newInstance(); 複制代碼
Class 類中的 newInstance 方法被棄用,原因是它存在一些問題和局限性。主要問題如下:
異常處理不明确:Class 類的 newInstance 方法隻能抛出 InstantiationException 和 IllegalAccessException 異常。當調用構造方法時,如果構造方法本身抛出了異常,newInstance 會将該異常包裝為 InvocationTargetException,然後将其設定為 InstantiationException 的原因(cause),這使得異常處理變得複雜和不直覺。
僅支援無參構造方法:Class 類的 newInstance 方法隻能調用類的無參(預設)構造方法。這對于需要使用帶參數構造方法的類來說是不夠靈活的。
為了解決這些問題,Java 引入了 Constructor 類的 newInstance 方法。它具有以下優勢:
異常處理更清晰:Constructor 類的 newInstance 方法可以直接抛出 InvocationTargetException,使得異常處理更直接和簡單。
支援帶參數構造方法:Constructor 類的 newInstance 方法可以處理帶參數的構造方法,使其具有更好>的靈活性。
支援通路控制:通過 Constructor 類的 setAccessible 方法,可以在必要時通路私有構造方法。
反射的常見面試題
- 什麼是 Java 反射?
- 反射是 Java 語言提供的一種機制,允許在運作時檢查、通路和操作類、方法、字段和構造方法等元素。使用反射,可以在程式運作時動态加載類、建立對象、調用方法和通路字段。
- Java 反射的主要用途是什麼?
- Java 反射的主要用途包括:
- 動态加載類和建立對象
- 動态調用方法
- 動态通路和修改字段
- 擷取類的中繼資料(例如類名、方法、字段、注解等)
- 實作動态代理
- 如何使用反射擷取類的執行個體?
- 使用 Class.forName("類的全限定名") 方法動态加載類,然後使用 Constructor.newInstance() 方法建立類的執行個體。
- 如何使用反射調用方法?
- 使用 Class.getDeclaredMethod() 方法擷取特定的方法,然後使用 Method.invoke() 方法調用該方法。
- 如何使用反射通路字段?
- 使用 Class.getDeclaredField() 方法擷取特定的字段,然後使用 Field.get() 和 Field.set() 方法分别擷取和設定字段的值。
- 反射的性能問題?
- 反射操作相對于直接操作會有一定的性能損失,因為反射涉及到運作時類型檢查、方法調用等額外操作。在性能敏感的場景下應謹慎使用反射。但在很多場景中,反射帶來的靈活性和可擴充性可以抵消性能損失。
- 什麼是動态代理?如何使用反射實作動态代理?
- 動态代理是一種在運作時動态建立代理對象的技術,代理對象可以實作指定的接口。Java 反射提供了 java.lang.reflect.Proxy 類來實作動态代理。通過 Proxy.newProxyInstance() 方法可以建立代理對象,需要提供一個實作了 InvocationHandler 接口的對象來處理代理方法的調用。
作者:小新x
連結:https://juejin.cn/post/7223280402474926138
來源:稀土掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。