摘要: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<泛型>的 ==號比較有關。
因為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
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個整數。
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做快取,而不是每次都去查詢。
Q:
getMethods()後自己做遍歷獲取方法,和getMethod(methodName) 直接獲取方法, 為什麼效能會有差異?
A:
getMethods() 返回method陣列時,每個method都做了一次拷貝。 getMethod(methodName)只會返回那個方法的拷貝, 效能的差異就體現在拷貝上。
Q:
獲取方法時,jvm內部其實有快取,但是返回給外部時依然會做拷貝。那麼該method的快取是持久存在的嗎?
A:
不是持久存在的,記憶體不足時會被回收。原始碼如下:
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); }
可以看到這是一個軟引用。
軟引用的定義:記憶體緊張時可能會被回收,不過也可以通過-XX:SoftRefLRUPolicyMSPerMB引數控制回收的時機,只要發生GC就會將其回收。
如果reflectionData被回收之後,又執行了反射方法,那隻能通過newReflectionData方法重新建立一個這樣的物件了。
Q: 反射是執行緒安全的嗎?
A:
是執行緒安全的。 獲取反射的資料時,通過cas去獲取。 cas概念可以見多執行緒一節。
Q:
a普通方法呼叫
b反射方法呼叫
c關閉安全檢查的反射方法呼叫,效能差異如下:
b反射方法呼叫和c關閉安全檢查的反射方法呼叫的效能差異在哪?普通方法呼叫和關閉安全檢查的反射方法呼叫的效能差異在哪?
A:
- 安全檢查的效能消耗在於
,SecurityManager.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION); 這項檢測需要執行時申請RuntimePermission(“accessDeclaredMembers”)。 所以如果不考慮安全檢查, 對反射方法呼叫invoke時, 應當設定 Method#setAccessible(true) - 普通方法和反射方法的效能差異在於
- Method#invoke 方法會對引數做封裝和解封操作
- 需要檢查方法可見性
- 需要校驗引數
- 反射方法難以內聯
- JIT 無法優化
本文分享自華為雲社群《java知識點問題精選之反射》,原文作者:breakDraw 。