天天看點

JAVA反射常見面試問題

作者:疆為

1、什麼是 Java 反射機制?

Java 反射機制是指在運作時動态擷取一個類的資訊,包括類的屬性、方法、構造方法等,并且可以通過反射機制建立對象、調用方法、修改屬性等。

2、反射機制的優缺點是什麼?

反射機制的優點是可以在運作時擷取類的資訊并動态建立對象、調用方法等,使得程式更加靈活。

缺點是會降低程式的性能,且容易破壞程式的封裝性和安全性。

3、反射機制能用來做什麼?

反射機制可以用來擷取類的資訊、建立對象、調用方法、修改屬性等,可以在運作時動态地操作類和對象。

4 反射的使用場景

Java反射機制可以在運作時擷取類的資訊并對其進行操作,常見的使用場景包括:

  1. 通過類的全限定名動态加載類,例如:在JDBC中加載資料庫驅動程式。
  2. 擷取類的所有方法、字段、構造方法、注解等資訊,并對其進行操作。
  3. 實作注解處理器,例如:Spring架構中的注解處理器,用于處理注解标記的類和方法,實作依賴注入、AOP等功能。
  4. 實作動态代理,例如:JDK的動态代理機制、CGLIB等,實作面向切面程式設計、RPC等功能。
  5. 調用私有方法或字段,例如:測試架構中的測試私有方法,或者破解一些限制。
  6. 反射調用構造方法建立對象,例如:在Spring架構中使用反射調用無參構造方法建立對象并進行依賴注入。

總之,Java反射機制提供了一種靈活、動态的方式來擷取類的資訊并對其進行操作,可以在許多場景下使用,但由于反射調用的性能相對較低,應謹慎使用,盡量在必要時使用,避免濫用。

5、如何通過反射擷取一個類的執行個體?

1、 使用 Class 類的 newInstance() 方法。這個方法會調用類的無參構造函數來建立執行個體。例如:

Class<?> clazz = Class.forName("com.example.MyClass"); 
Object obj = clazz.newInstance();           

2、 如果類沒有預設的無參構造函數,可以使用 getConstructor() 方法擷取指定參數類型的構造函數,并調用 Constructor 類的 newInstance() 方法來建立執行個體。例如:

Class<?> clazz = Class.forName("com.example.MyClass"); 
Constructor<?> constructor = clazz.getConstructor(int.class, String.class); 
Object obj = constructor.newInstance(123, "hello");
           

在上面的代碼中,我們通過 getConstructor() 方法擷取了一個具有 int 和 String 兩個參數的構造函數,然後使用 newInstance() 方法建立了執行個體。 3、 如果類隻有私有的構造函數,也可以通過反射建立執行個體。首先需要調用 getDeclaredConstructor() 方法擷取構造函數,并設定 setAccessible(true) 來打開構造函數的通路權限。例如:

Class<?> clazz = Class.forName("com.example.MyClass");
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object obj = constructor.newInstance();           

在上面的代碼中,我們首先通過 getDeclaredConstructor() 方法擷取了一個私有的構造函數,然後設定 setAccessible(true) 來打開構造函數的通路權限,并使用 newInstance() 方法建立執行個體。 需要注意的是,使用反射建立對象時,需要處理可能會抛出的 ClassNotFoundException、InstantiationException、IllegalAccessException、InvocationTargetException、NoSuchMethodException 等異常。

6、如何通過反射擷取類的屬性和方法?

可以通過調用 Class 類的 getDeclaredField() 和 getDeclaredMethod() 方法來擷取類的屬性和方法。例如:

vbnetCopy code
Class<?> clazz = MyClass.class;
Field field = clazz.getDeclaredField("myField");
Method method = clazz.getDeclaredMethod("myMethod");            

7、如何通過反射調用一個方法?

Class<?> clazz = MyClass.class;
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("myMethod");
method.invoke(obj);           

8、什麼場景下會用反射調用一個類的方法

反射調用一個方法通常在以下場景下使用:

在設計通用代碼時,無法确定需要調用的方法是哪個,這時可以使用反射動态擷取類的資訊并調用對應的方法。

在架構開發中,有時需要在運作時動态調用類的方法,這時可以使用反射機制來實作。

在測試代碼中,有時需要測試私有方法或者一些不容易測試的方法,這時可以使用反射來調用這些方法。

在需要動态建立對象的場景下,可以使用反射來建立對象并調用其方法。

總的來說,反射可以在運作時動态地擷取類的資訊并進行動态調用,這樣可以增強程式的靈活性和可擴充性。但是由于反射調用方法的性能相對較低,是以在性能要求較高的場景下應謹慎使用。

9、什麼是動态代理?如何使用反射實作動态代理?

動态代理是指在運作時動态地生成代理類來替代原始類,以達到增加功能或控制原始類行為的目的。可以使用反射機制來實作動态代理,通過在運作時生成代理類并在代理類中調用目标方法實作代理。

10、反射和泛型的關系是什麼?

反射和泛型是兩個獨立的概念,但是反射可以用來處理泛型類型。通過反射可以擷取泛型的類型參數資訊,并在運作時動态地建立泛型對象。

11、 如何避免反射帶來的安全問題?

反射機制可以在運作時擷取類的資訊并對其進行操作,但是在一些情況下,反射可能會引發安全問題。例如,使用反射可以擷取私有方法和字段,并調用它們,這可能會破壞程式的安全性。

為了避免反射帶來的安全問題,可以采取以下措施:

  1. 使用安全管理器對反射進行限制,可以限制反射的通路權限,避免惡意代碼通過反射擷取敏感資訊。
  2. 對于使用者輸入的資料,應該進行嚴格的校驗和過濾,避免使用者輸入惡意代碼,例如SQL注入、XSS攻擊等。
  3. 對于關鍵代碼,可以使用代碼混淆等技術,避免惡意攻擊者通過反射擷取關鍵資訊。
  4. 對于不需要對外暴露的方法或字段,應該使用private或protected修飾符,并盡量減少使用public修飾符。
  5. 對于敏感資訊,可以使用加密、哈希等技術進行保護,避免惡意攻擊者通過反射擷取敏感資訊。

例如,假設我們有一個銀行應用程式,其中有一個私有方法用于調用存款操作。如果不加控制地使用反射機制調用該私有方法,則可能會導緻惡意攻擊者通過反射機制繞過存款操作的安全性檢查,直接進行存款操作,導緻銀行賬戶被盜。為了避免這種情況,可以使用安全管理器限制反射的通路權限,并對輸入的存款金額進行校驗和過濾,避免惡意攻擊。

12、 反射機制對性能有什麼影響?如何優化?

使用反射機制對程式的性能有一定的影響,因為反射需要在運作時進行類型檢查和方法調用等操作,這些操作會導緻一定的性能損失。特别是在需要頻繁使用反射的情況下,性能影響會更加顯著。

為了優化反射帶來的性能影響,可以采取以下措施:

  1. 盡量避免在核心業務邏輯中頻繁使用反射,可以使用其他方式實作相同的功能,例如緩存或者預先加載對象等。
  2. 在使用反射時,盡量減少類型轉換等操作,避免不必要的性能損失。
  3. 對于一些重要的對象,可以使用預先加載或者懶加載的方式,避免在程式運作時頻繁使用反射加載和初始化對象。
  4. 使用動态代理等技術,可以在一定程度上減少反射的使用,進而提高程式的性能。

以下是一個簡單的例子,示範了如何使用緩存和預先加載的方式來優化反射的性能:

public class ReflectionExample {
    private static Map<String, Class<?>> classCache = new HashMap<>();

    // 使用緩存和預先加載的方式來建立對象
    public static Object createObject(String className) throws Exception {
        Class<?> clazz = classCache.get(className);
        if (clazz == null) {
            clazz = Class.forName(className);
            classCache.put(className, clazz);
        }
        return clazz.newInstance();
    }

    public static void main(String[] args) throws Exception {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            Object obj = createObject("java.lang.String");
            Method method = obj.getClass().getMethod("length");
            method.invoke(obj);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("Time elapsed: " + (endTime - startTime) + " ms");
    }
}

            

繼續閱讀