天天看點

一篇文章帶你打開面向對象程式設計的新大門!即掌握Java反射

開篇之前先來一道面試題:請問 Java 中的反射是什麼?有什麼作用和優勢?如何使用反射?

你們可以先嘗試回答回答

如果不會我們就開始下面的文章

引言

什麼是反射

Java反射(Reflection)是指在運作時擷取對象資訊(例如類名、方法、屬性等),并且可以動态操作該對象,而無需事先知道該對象的靜态類型。它允許程式在運作時檢查對象和類,并且可以調用對象的方法、擷取對象的屬性和構造函數,甚至可以動态建立新的對象和數組。

反射的作用

Java反射的作用是使得程式能夠在運作時動态地操作對象和類,而不需要事先知道這些資訊,這種能力為程式設計帶來了很大的靈活性。使用反射可以實作很多進階功能,比如動态代理、注解處理、JavaBean屬性的自動設定等等。但是,由于反射操作具有較高的開銷,是以在需要性能的場合應該謹慎使用。

背景知識

Java反射的基本原理是通過使用Java的反射API來擷取類的資訊。Java的反射API提供了一個Class類,它可以用來描述類的資訊,例如類名、構造函數、方法和字段等。通過使用Class類的方法,可以擷取一個類的所有資訊。

Java反射可以讓程式在運作時動态地擷取類的資訊并進行操作,這使得程式的靈活性更高。但是,由于反射會犧牲一定的性能,是以在性能要求較高的情況下,應該盡量避免使用反射。

講解Java反射的核心概念和API

Java反射的核心概念主要涉及到以下幾個類:Class、Constructor、Field 和 Method。以下是這些類的主要API及其用法:

  1. Class類
  2. 作用:表示正在運作的Java應用程式中的類和接口。
  3. 擷取Class對象的方法:
  4. Class.forName(String className):根據類名加載類并擷取Class對象。
  5. object.getClass():根據已知對象擷取Class對象。
  6. ClassName.class:直接擷取類的Class對象。
  7. Constructor類
  8. 作用:描述類的構造方法。
  9. 擷取構造方法對象的方法:
  10. Class.getConstructor(Class<?>... parameterTypes):擷取類的public構造方法。
  11. Class.getDeclaredConstructor(Class<?>... parameterTypes):擷取類的所有構造方法,包括private的。
  12. 建立對象:
  13. Constructor.newInstance(Object... initargs):使用構造方法建立新的對象執行個體。
  14. Field類
  15. 作用:描述類的字段(成員變量)。
  16. 擷取字段對象的方法:
  17. Class.getField(String name):擷取類的public字段。
  18. Class.getDeclaredField(String name):擷取類的所有字段,包括private的。
  19. 擷取和設定字段值:
  20. Field.get(Object obj):擷取對象的字段值。
  21. Field.set(Object obj, Object value):設定對象的字段值。
  22. Method類
  23. 作用:描述類的方法。
  24. 擷取方法對象的方法:
  25. Class.getMethod(String name, Class<?>... parameterTypes):擷取類的public方法。
  26. Class.getDeclaredMethod(String name, Class<?>... parameterTypes):擷取類的所有方法,包括private的。
  27. 調用方法:
  28. 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");
複制代碼           
  1. 擷取類的名稱:
  2. String getName():傳回類的全限定名(包含包名)。
  3. String getSimpleName():傳回類的簡單名(不包含包名)。
  4. String getCanonicalName():傳回類的規範名(類似于全限定名,但對于内部類和數組類有所不同)。
  5. 加載和擷取類:
  6. static Class<?> forName(String className):根據類名加載類并擷取其 Class 對象。
  7. static Class<?> forName(String name, boolean initialize, ClassLoader loader):根據類名、是否初始化以及類加載器加載類并擷取其 Class 對象。
  8. 擷取類的修飾符:
  9. int getModifiers():傳回類的修飾符,例如 public、private、abstract 等。
  10. Modifier這個類裡有判斷傳回數字是什麼類型的方法
  11. 擷取類的父類和接口:
  12. Class<?> getSuperclass():傳回類的父類。
  13. Class<?>[] getInterfaces():傳回類實作的接口。
  14. 擷取類的構造方法、字段和方法:
  15. Constructor<?>[] getConstructors():傳回類的 public 構造方法。
  16. Constructor<?>[] getDeclaredConstructors():傳回類的所有構造方法,包括 private 的。
  17. Field[] getFields():傳回類的 public 字段。
  18. Field[] getDeclaredFields():傳回類的所有字段,包括 private 的。
  19. Method[] getMethods():傳回類的 public 方法。
  20. Method[] getDeclaredMethods():傳回類的所有方法,包括 private 的。
  21. 擷取類的注解:
  22. Annotation[] getAnnotations():傳回類的注解。
  23. Annotation[] getDeclaredAnnotations():傳回類的所有注解,包括繼承自父類的。
  24. T getAnnotation(Class<T> annotationClass):傳回類的指定類型的注解。
  25. 建立類的執行個體:
  26. T newInstance():使用預設構造方法建立類的執行個體。在 Java 9 之後被棄用,建議使用 Constructor.newInstance()。
  27. 判斷類的關系:
  28. boolean isAssignableFrom(Class<?> cls):判斷目前類是否為參數類的超類或接口。
  29. boolean isInstance(Object obj):判斷指定對象是否為目前類的執行個體。
  30. boolean isInterface():判斷目前類是否為接口。
  31. boolean isPrimitive():判斷目前類是否為基本類型。
  32. boolean isArray():判斷目前類是否為數組。

Constructor

Constructor 類是 Java 反射中的一個重要類,用于表示類的構造方法。以下是 Constructor 類中的一些重要方法:

  1. 擷取所有的構造方法:使用 Class 類的 getConstructors() 方法擷取類的所有公共構造方法,或使用 getDeclaredConstructors() 方法擷取類的所有構造方法(包括私有、受保護和預設通路權限的構造方法)。 示例:
Class<?> clazz = SomeClass.class;
Constructor<?>[] constructors = clazz.getConstructors(); // 擷取所有公共構造方法
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); // 擷取所有構造方法(包括非公共構造方法)
複制代碼           
  1. 擷取特定的構造方法:使用 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());
}

複制代碼           
  1. 擷取構造方法的名稱和修飾符:
  2. String getName():傳回構造方法的名稱,即類名。
  3. int getModifiers():傳回構造方法的修飾符,例如 public、private、protected 等。
  4. 擷取構造方法的參數類型和異常類型:
  5. Class<?>[] getParameterTypes():傳回構造方法的參數類型。
  6. Type[] getGenericParameterTypes():傳回構造方法的泛型參數類型。
  7. Class<?>[] getExceptionTypes():傳回構造方法抛出的異常類型。
  8. Type[] getGenericExceptionTypes():傳回構造方法抛出的泛型異常類型。
  9. 擷取和設定構造方法的通路權限:
  10. boolean isAccessible():傳回構造方法的可通路狀态。
  11. void setAccessible(boolean flag):設定構造方法的可通路狀态。如果要通路 private 構造方法,需要将 flag 設定為 true。
  12. 建立類的執行個體:
  13. T newInstance(Object... initargs):使用構造方法建立類的執行個體。initargs 參數表示構造方法的參數值。
  14. 擷取構造方法的注解:
  15. Annotation[] getAnnotations():傳回構造方法的注解。
  16. Annotation[] getDeclaredAnnotations():傳回構造方法的所有注解。
  17. T getAnnotation(Class<T> annotationClass):傳回構造方法的指定類型的注解。
  18. Annotation[][] getParameterAnnotations():傳回構造方法參數的注解。

Field

Field 類是 Java 反射中的一個重要類,用于表示類的字段(成員變量)。以下是 Field 類中的一些重要方法:

  1. 擷取字段的名稱和修飾符:
  2. String getName():傳回字段的名稱。
  3. int getModifiers():傳回字段的修飾符,例如 public、private、protected、static 等。
  4. 擷取字段的類型:
  5. Class<?> getType():傳回字段的類型。
  6. Type getGenericType():傳回字段的泛型類型。
  7. 擷取和設定字段的值:
  8. Object get(Object obj):傳回指定對象上該字段的值。對于靜态字段,obj 參數可以為 null。
  9. void set(Object obj, Object value):為指定對象上的該字段設定值。對于靜态字段,obj 參數可以為 null。
  10. 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。
  11. 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。
  12. 擷取和設定字段的通路權限:
  13. boolean isAccessible():傳回字段的可通路狀态。
  14. void setAccessible(boolean flag):設定字段的可通路狀态。如果要通路 private 字段,需要将 flag 設定為 true。
  15. 擷取字段的注解:
  16. Annotation[] getAnnotations():傳回字段的注解。
  17. Annotation[] getDeclaredAnnotations():傳回字段的所有注解。
  18. T getAnnotation(Class<T> annotationClass):傳回字段的指定類型的注解。

Method

Method 類是 Java 反射中的一個重要類,用于表示類的方法。以下是 Method 類中的一些重要方法:

  1. 擷取方法的名稱和修飾符:
  2. String getName():傳回方法的名稱。
  3. int getModifiers():傳回方法的修飾符,例如 public、private、protected、static 等。
  4. 擷取方法的參數類型和傳回類型:
  5. Class<?>[] getParameterTypes():傳回方法的參數類型。
  6. Type[] getGenericParameterTypes():傳回方法的泛型參數類型。
  7. Class<?> getReturnType():傳回方法的傳回類型。
  8. Type getGenericReturnType():傳回方法的泛型傳回類型。
  9. 擷取方法的異常類型:
  10. Class<?>[] getExceptionTypes():傳回方法抛出的異常類型。
  11. Type[] getGenericExceptionTypes():傳回方法抛出的泛型異常類型。
  12. 調用方法:
  13. Object invoke(Object obj, Object... args):調用指定對象上的該方法。obj 參數表示方法所在的對象,args 參數表示方法的參數值。對于靜态方法,obj 參數可以為 null。
  14. 擷取和設定方法的通路權限:
  15. boolean isAccessible():傳回方法的可通路狀态。
  16. void setAccessible(boolean flag):設定方法的可通路狀态。如果要通路 private 方法,需要将 flag 設定為 true。
  17. 擷取方法的注解:
  18. Annotation[] getAnnotations():傳回方法的注解。
  19. Annotation[] getDeclaredAnnotations():傳回方法的所有注解。
  20. T getAnnotation(Class<T> annotationClass):傳回方法的指定類型的注解。
  21. Annotation[][] getParameterAnnotations():傳回方法參數的注解。

建立執行個體

  1. 使用預設構造方法建立執行個體:
// 加載類
Class<?> cls = Class.forName("Reflection.bean.Person");

// 擷取預設構造方法(無參構造方法)
Constructor<?> constructor = cls.getDeclaredConstructor();

// 設定構造方法的通路權限,如果構造方法是私有的,則需要設定為 true
constructor.setAccessible(true);

// 建立執行個體
Object obj = constructor.newInstance();
複制代碼           
  1. 使用帶參數的構造方法建立執行個體:
// 加載類
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 方法,可以在必要時通路私有構造方法。

反射的常見面試題

  1. 什麼是 Java 反射?
  2. 反射是 Java 語言提供的一種機制,允許在運作時檢查、通路和操作類、方法、字段和構造方法等元素。使用反射,可以在程式運作時動态加載類、建立對象、調用方法和通路字段。
  3. Java 反射的主要用途是什麼?
  4. Java 反射的主要用途包括:
  5. 動态加載類和建立對象
  6. 動态調用方法
  7. 動态通路和修改字段
  8. 擷取類的中繼資料(例如類名、方法、字段、注解等)
  9. 實作動态代理
  10. 如何使用反射擷取類的執行個體?
  11. 使用 Class.forName("類的全限定名") 方法動态加載類,然後使用 Constructor.newInstance() 方法建立類的執行個體。
  12. 如何使用反射調用方法?
  13. 使用 Class.getDeclaredMethod() 方法擷取特定的方法,然後使用 Method.invoke() 方法調用該方法。
  14. 如何使用反射通路字段?
  15. 使用 Class.getDeclaredField() 方法擷取特定的字段,然後使用 Field.get() 和 Field.set() 方法分别擷取和設定字段的值。
  16. 反射的性能問題?
  17. 反射操作相對于直接操作會有一定的性能損失,因為反射涉及到運作時類型檢查、方法調用等額外操作。在性能敏感的場景下應謹慎使用反射。但在很多場景中,反射帶來的靈活性和可擴充性可以抵消性能損失。
  18. 什麼是動态代理?如何使用反射實作動态代理?
  19. 動态代理是一種在運作時動态建立代理對象的技術,代理對象可以實作指定的接口。Java 反射提供了 java.lang.reflect.Proxy 類來實作動态代理。通過 Proxy.newProxyInstance() 方法可以建立代理對象,需要提供一個實作了 InvocationHandler 接口的對象來處理代理方法的調用。

作者:小新x

連結:https://juejin.cn/post/7223280402474926138

來源:稀土掘金

著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

繼續閱讀