Java程式設計師都要懂得知識點:反射

華為雲開發者社群發表於2021-03-29
摘要:Java反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。

Java反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。

反射就是把java類中的各種成分對映成一個個的Java物件。

例如:一個類有:成員變數、方法、構造方法、包等等資訊,利用反射技術可以對一個類進行解剖,把個個組成部分對映成一個個物件。

(其實:一個類中這些成員方法、構造方法、在加入類中都有一個類來描述)

反射

Q: 呼叫類物件.class 和 forName(類名)的區別?

Class<A> classA = A.class;
Class<A> classA = Class.forName("A");

A: 僅使用.class不能進行第一次靜態初始化, forname函式則可以

例如B是A的基類,下面這段程式碼如何?
假設有父子2個類,如下:

static class Parent { }

static class Son extends Parent{}

Q: 用instanceof 可以和父類比較嗎,且會返回true嗎?

   Son son = new Son();
        if (son instanceof  Parent) {
            System.out.println("a instanof B");
        }

A: 可以比較,且返回true。

Q: 用getClass並用== 可以和父類比較嗎,且會返回true嗎,下面這樣:
注意A是B的子類。

  Son son = new Son();
        if (son.getClass() == Parent.class){
            System.out.println("son class == Parent.class");
        }

A: 不可以,編譯就會報錯了。和Class<泛型>的 ==號比較有關。

Java程式設計師都要懂得知識點:反射

因為getClass返回的是<? extends Son>, .class返回的是Class<Parent>

Q: 用getClass並用.equals可以和父類比較嗎,且會返回true嗎,下面這樣:

         Son son = new Son();
        if (son.getClass().equals(Parent.class)){
            System.out.println("son class.equals(Parent.class)");
        }

A: 可以比較,正常編譯, 但是會返回false,即不相等!

Q: getDeclaredXXX 有哪幾種?
A: 5種:

  • 註解Annotation
  • 內部類Classed
  • 構造方法Construcotor
  • 欄位Field
  • 方法Method

Java程式設計師都要懂得知識點:反射

Q:getMethods()返回哪些方法, getDeclaredMethods()會返回哪些方法?

A:
getMethods()返回 本類、父類、父介面 的public方法
getDeclaredMethods()只 返回本類的 所有 方法

其他getXXX和getDeclaredXXX的區別同理。

拿到Filed、Method、Constructor之後咋用

  • Method可以invoke(object, args)
  • Constructor可以newInstance(Object…)來做構造呼叫。
  • Filed可以用get(object)、set(object)來設定屬性值。

Q: 反射拿到Method物件後, 該物件.getModifiers() 是幹嘛的?
A: 返回該方法的修飾符,並且是1個整數。

Java程式設計師都要懂得知識點:反射

Q:
下面這段程式碼會發生什麼?

package com.huawei.test

public class A {
    public A(int i ) {
        System.out.printf("i=" +i);
    }

    public static void main(String[] args) {
        try {
            A a = (A)Class.forName("com.huawei.test.A").newInstance();
        } catch (ClassNotFoundException e) {
            System.out.printf("ClassNotFoundException");
        } catch (InstantiationException e) {
            System.out.printf("InstantiationException");
        } catch (IllegalAccessException e) {
            System.out.printf("IllegalAccessException");
        }
    }
}

A:
列印InstantiationException初始化錯誤。因為A沒有預設構造器了,所以不可以用newInstance來構造。應該改成這樣,通過獲取正確的構造器來進行構造。

A a = (A)Class.forName("A").getConstructor(int.class).newInstance(123);

Q:如何提高反射的效率?
A:

  • 使用高效能反射包,例如ReflectASM
  • 快取反射的物件,避免每次都要重複去位元組碼中獲取。(快取!快取!)
  • method反射可設定method.setAccessible(true)來關閉安全檢查。
  • 儘量不要getMethods()後再遍歷篩選,而直接用getMethod(methodName)來根據方法名獲取方法
  • 利用hotspot虛擬機器中的反射優化技術(jit技術)
    參考資料:
    https://segmentfault.com/q/1010000003004720
    https://www.cnblogs.com/coding-night/p/10772631.html

Q:
用反射獲取到的method物件, 是返回一個method引用,還是返回1個拷貝的method物件?
A:
反射拿method物件時, 會做一次拷貝,而不是直接返回引用,因此最好對頻繁使用的同一個method做快取,而不是每次都去查詢。

Java程式設計師都要懂得知識點:反射

Q:
getMethods()後自己做遍歷獲取方法,和getMethod(methodName) 直接獲取方法, 為什麼效能會有差異?
A:
getMethods() 返回method陣列時,每個method都做了一次拷貝。 getMethod(methodName)只會返回那個方法的拷貝, 效能的差異就體現在拷貝上。

Java程式設計師都要懂得知識點:反射

Q:
獲取方法時,jvm內部其實有快取,但是返回給外部時依然會做拷貝。那麼該method的快取是持久存在的嗎?
A:
不是持久存在的,記憶體不足時會被回收。原始碼如下:

Java程式設計師都要懂得知識點:反射

private Class.ReflectionData<T> reflectionData() {
    SoftReference<Class.ReflectionData<T>> reflectionData = this.reflectionData;
    int classRedefinedCount = this.classRedefinedCount;
    Class.ReflectionData rd;
    return reflectionData != null && (rd = (Class.ReflectionData)reflectionData.get()) != null
    && rd.redefinedCount == classRedefinedCount ? rd : this.newReflectionData(reflectionData,     classRedefinedCount);
}

Java程式設計師都要懂得知識點:反射

可以看到這是一個軟引用。

軟引用的定義:記憶體緊張時可能會被回收,不過也可以通過-XX:SoftRefLRUPolicyMSPerMB引數控制回收的時機,只要發生GC就會將其回收。

如果reflectionData被回收之後,又執行了反射方法,那隻能通過newReflectionData方法重新建立一個這樣的物件了。

Q: 反射是執行緒安全的嗎?
A:
是執行緒安全的。 獲取反射的資料時,通過cas去獲取。 cas概念可以見多執行緒一節。

Java程式設計師都要懂得知識點:反射

Q:
a普通方法呼叫
b反射方法呼叫
c關閉安全檢查的反射方法呼叫,效能差異如下:

Java程式設計師都要懂得知識點:反射

b反射方法呼叫和c關閉安全檢查的反射方法呼叫的效能差異在哪?普通方法呼叫和關閉安全檢查的反射方法呼叫的效能差異在哪?
A:

  • 安全檢查的效能消耗在於
    ,SecurityManager.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION); 這項檢測需要執行時申請RuntimePermission(“accessDeclaredMembers”)。 所以如果不考慮安全檢查, 對反射方法呼叫invoke時, 應當設定 Method#setAccessible(true)
  • 普通方法和反射方法的效能差異在於
  1. Method#invoke 方法會對引數做封裝和解封操作
  2. 需要檢查方法可見性
  3. 需要校驗引數
  4. 反射方法難以內聯
  5. JIT 無法優化

 本文分享自華為雲社群《java知識點問題精選之反射》,原文作者:breakDraw 。

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章