反射是什么
反射(Reflection)是Java程序開發語言的特征之一,它允許運行中的Java程序獲取自身的信息,并且可以操作類或對象的內部屬性。主要是指程序可以訪問、檢測和修改它本身狀態或行為的一種能力,并能根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。
Oracle官方對反射的解釋:
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.
簡而言之,通過反射,我們可以在運行時獲得程序或程序集中每一個類型的成員和成員的信息。
使用反射有如下優缺點
優點:
- 能夠運行時動態獲取類的實例,大大提高系統的靈活性和擴展性
- android的開發中,反射使得在版本和機型的適配兼容性上提供了大的方便
缺點:
- 使用反射的性能較低
- 使用反射相對來說不安全(另外,對于混淆類的情況很可能對應不上)
- 破壞了類的封裝性(可以通過反射獲取這個類的私有方法和屬性 )
因此,反射有利有弊,需要分具體場景來擇取是否真的需要反射!!!
java虛擬機實例化一個對象的流程描述:
假如你寫了一段代碼:Object o = new Object();
運行了起來!
其內部運行流程為如下描述:
首先JVM會啟動,你的代碼會編譯成一個.class文件,然后被類加載器加載進jvm的內存中,你的類Object加載到方法區中,創建了Object類的class對象到堆中,注意這個不是new出來的對象,而是類的類型對象,每個類只有一個class對象,作為方法區類的數據結構的接口。jvm創建對象前,會先檢查類是否加載,尋找類對應的class對象,若加載好,則為你的對象分配內存,初始化也就是代碼:new Object()。
以上流程來自于這里的描述: https://www.zhihu.com/question/24304289
JDK反射介紹
在這里先看一下sun為我們提供了哪些操作反射的類:
java.lang.Class; //類對象
java.lang.reflect.Constructor; //構造器對象
java.lang.reflect.Field; //屬性對象
java.lang.reflect.Method; //方法對象
java.lang.reflect.Modifier; //修飾符對象
注:Modifier
可用來描述abstract
、final
、interface
、native
、private
、protected
、public
、static
、strictfp
、synchronized
、transient
、volatile
等。
JDK反射具體有如下用法:
注意區分帶不帶Declared
字樣的方法名區別:
- 帶有
Declared時
表示獲取與public
、private
、protect
修飾無關的所有對象,但不能是繼承來的; - 不帶
Declared
時,表示只獲取public
標識符修飾的對象,且還包括從超類繼承來的public
對象。
- 獲取對象構造器
//獲得指定參數類型params的public構造函數
Constructor getConstructor(Class[] params);
//獲得類的所有public構造函數
Constructor[] getConstructors();
//獲得使用指定參數類型params的構造函數
Constructor getDeclaredConstructor(Class[] params);
//獲得類的所有的構造函數
Constructor[] getDeclaredConstructors();
- 獲取類屬性字段
//獲得類中命名為name的public字段
Field getField(String name);
//獲得類的所有public字段
Field[] getFields();
//獲得類中命名為name的字段
Field getDeclaredField(String name);
//獲得類中的所有的字段
Field[] getDeclaredFields();
- 獲取類方法
//使用指定參數類型params,獲得名稱為name的public方法
Method getMethod(String name, Class[] params);
//獲得類中所有的public方法
Method[] getMethods();
//使用指定參數類型params,獲得命名為name的方法
Method getDeclaredMethod(String name, Class[] params);
//獲取類中的所有方法
Method[] getDeclaredMethods();
android開發中常見用法如下(其他用法不再進行測試描述,具體可以參見GH-Demo):
public static int getStatusBarHeight(Context context) {
try {
//構造dimen類對象
Class<?> c = Class.forName("com.android.internal.R$dimen");
Object obj = c.newInstance(); //使用無參構造函數實例化dimen對象
Field field = c.getField("status_bar_height"); //獲取obj對象中名稱為status_bar_height的Field字段
field.setAccessible(true); //修改訪問修飾屬性(假設為private修飾)
int height = Integer.parseInt(field.get(obj).toString()); //從obj對象中讀取field字段對應的value
return context.getResources().getDimensionPixelSize(height);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return 0;
}
下面介紹一下網上比較熱門且自認為用法比較靈活的java反射封裝類庫:jOOR
jOOR官方介紹
jOOR - Fluent Reflection in Java jOOR is a very simple fluent API that gives access to your Java Class structures in a more intuitive way. The JDK’s reflection APIs are hard and verbose to use. Other languages have much simpler constructs to access type meta information at runtime. Let us make Java reflection better. http://www.jooq.org/products
大意為:jOOR提供了一種更為直觀的方式來構建JDK原生的反射調用,因為JDK提供的反射API使用起來較冗長(它對包java.lang.reflect進行了簡單封裝,使得反射更加方便)。
gradle配置為:compile'org.jooq:joor:0.9.6'
先看一個簡單例子(同上述方法getStatusBarHeight()
),對比JDK
的反射API和jOOR
的方法調用。
public static int getStatusBarHeight(Context context) {
final int statusHeightId = Reflect.on("com.android.internal.R$dimen")
.create().field("status_bar_height").get();
return context.getResources().getDimensionPixelSize(statusHeightId);
}
從上面的例子就可以看到jOOR
明顯的優勢:
- 簡化了反射冗長的異常處理。
- 簡化了對
Class
、Method
等反射類的實例化,改為統一Reflect
替換。 - 支持
private
方法的調用,內部動態區分是否需要accessible()
。 - 將反射調用封裝為更流行的鏈式調用方式,代碼更容易理解(類似
Rxjava
的封裝方式)。
jOOR功能介紹
- 提供
on()
操作符對Class
、對象(還包括方法、構造函數)進行統一實例化為Reflect
對象,后續所有的反射操作基于該Reflect
對象進行。 - 調用方式均被封裝成返回
Reflect
對象的鏈式結構,在使用上使得代碼更加簡潔。 - 對方法的簽名匹配封裝了更完善的匹配規則,包括精確匹配
exactMethod()
、近似匹配similarMethod()
【對函數參數的近似匹配(int -> Integer
)】和基類搜索等。 - 調用私有方法的不需要顯示調用
setAccessible()
,內部動態讀取修飾標記自動適配。 - 更加簡潔的實現了對象構造函數的反射調用
create()
方法。 - 函數的調用
call()
方法組合成了可以拼接在Reflect
的對象后面的鏈式方法。 - 額外增加了高級操作符
as()
,它實現了類的代理訪問以及POJO對象
的get/set/is
方法實現。
jOOR API介紹
- 通過類名轉換成一個
Reflect
對象
public static Reflect on(String name);
public static Reflect on(String name, ClassLoader classLoader);
public static Reflect on(Class<?> clazz);
- 將一個對象轉換成一個
Reflect
對象
public static Reflect on(Object object);
- 修改一個
AccessibleObject
類型的對象的訪問權限
public static <T extends AccessibleObject> T accessible(T accessible);
- 返回
Reflect
對象具體包裝的類型,類型為Class
或者對象,具體由操作符on()
的重載參數決定
public <T> T get();
- 將
name
指定的field
轉換成一個Reflect
對象,后續對field
的操作變為對Reflect
對象的操作
public Reflect field(String name);
- 返回當前
Reflect
對象的所有field
屬性,并轉換成Reflect
對象的map
public Map<String, Reflect> fields();
- 修改(獲取)
field
屬性值
public Reflect set(String name, Object value);
public <T> T get(String name);
- 反射調用
name
指定的函數,此函數封裝了對函數的簽名的精準匹配和近似匹配
public Reflect call(String name);
public Reflect call(String name, Object... args);
- 反射調用指定類的構造函數,封裝了精準匹配和近似匹配
public Reflect create();
public Reflect create(Object... args);
- 返回當前
Reflect
對象封裝的對象類型
public Class<?> type();
- 給封裝對象創建一個代理訪問,還實現了對
POJO對象
的setXX/getXX/isxx
功能(此為Reflect
對象的高級功能)
public <P> P as(Class<P> proxyType);
jOOR 典型用法
先列出測試舉例TestField
類定義以便有個大概的概念:
聲明實例:TestField testField = new TestField();
public static class TestField {
/**
* private
*/
private int INT1 = new Integer(0);
private Integer INT2 = new Integer(2);
/**
* private & static
*/
private static int S_INT1 = new Integer(3);
private static Integer S_INT2 = new Integer(0);
/**
* private & final
*/
private final int F_INT3 = new Integer(4);
/**
* object
*/
private TestField I_DATA;
public void tetProxy(String message) {
Log.d("TestJOOR", "tetProxy: " + message);
}
}
代理接口POJOInterface
的定義:
public interface POJOInterface {
String substring(int beginIndex, int endIndex);
void tetProxy(String message);
void setFoo(String foo);
String getFoo();
void setBaz(String baz);
String getBaz();
}
POJO
對象的Map
實現:
構造實例 Map<String, Object> map = new TestBean();
private class TestBean extends HashMap<String, Object> {
private String baz;
public void setBaz(String baz) {
this.baz = "POJO-MyMap: " + baz;
}
public String getBaz() {
return baz;
}
}
- 調用
String
的非靜態方法(通過實例對象構造Reflect
)
String value = "1234";
String subString = Reflect.on((Object) value).call("substring", 0, 2).get();
//結果為:subString = "123";
- 調用
String
的靜態方法(通過String.class
類名構造Reflect
)
String valueOf = Reflect.on(String.class).call("valueOf", true).get();
//結果為:valueOf = "true";
- 修改
private
屬性
int init_int = Reflect.on(testField).get("INT2"); //init_int=2;
Reflect setReflect = Reflect.on(testField).set("INT2", 300);
int result = setReflect.field("INT2").get(); //result = 300;
- 修改
static
屬性
int sInit_int = Reflect.on(TestField.class).get("S_INT1"); //sInit_int = 3;
Reflect obj = Reflect.on("com.android.test.joor.TestJOOR$TestField").set("S_INT2", 500);
int sInt2 = obj.field("S_INT2").get(); //sInt2 = 500;
- 復雜鏈式修改多個屬性值
Reflect.on(testField).set("I_DATA", Reflect.on(TestField.class).create())
.field("I_DATA").set("INT1", 700).set("INT2", 800);
-
interface
實現類對象
的代理
String asResult = Reflect.on((Object) "abc").as(POJOInterface.class).substring(1, 2);
//測試結果: asResult = "bc";
Reflect.on(testField).as(POJOInterface.class).tetProxy("this is proxy test!!");
//測試結果:調用TestField類中的tetProxy()函數。
- 對于沒有實現
set/get
方法的Bean對象,對應的數據有以key-value
(key
為setXx()
的xx
后綴)的形式存放在HashMap
中
Reflect.on(map).as(POJOInterface.class).setFoo("abc");
int size = map.size(); //size = 1;
String value1 = (String) map.get("foo"); //value1 = "abc";
String value2 = Reflect.on(map).as(POJOInterface.class).getFoo(); //value2 = "abc";
- 已經實現了
set/get
方法的Bean對象,對應數據存放在Bean對象的字段中
Reflect.on(map).as(POJOInterface.class).setBaz("baz");
int size = map.size(); //size = 0; 沒有存放在hasMap中
String value4 = (String) map.get("baz"); //value4 = null;
String value5 = Reflect.on(map).as(POJOInterface.class).getBaz(); //value5 = "MyMap: baz-test";
備注:
jOOR
對反射的原始異常進行了轉義---ReflectException
,其直接基類為RuntimeException
,這里意味著通過jOOR
庫的api開發時,不會強制開發人員用try...catch
括起來,但一旦發生異常,程序就會中斷運行。
反射調用效率優化
從應用層面看,想對反射的執行效率做提升優化,只能在項目中明令禁止使用反射了,如果不可規避(一般避免不了),只能最大限度的降低反射的調用次數了。一般的做法是將第一次獲取的Class
、Method
、Field
對象進行緩存起來,下次調用同樣的反射對象時直接取已緩存對象進行相應調用。這里引用另一個反射調用的封裝庫 reflect,其內部就是將Class
、Method
、Field
等對象進行緩存以備下次調用,但個人認為其用法不如jOOR
靈活,但優化思路值得參考。
本人參考reflect庫的優化思路對jOOR
內部進行修改,在不破壞外部調用接口的前提下對內部的Class
、Constructor
、Method
、Field
進行緩存。地址為GH-Demo-Reflect.java
參考文檔
https://www.zhihu.com/question/24304289
https://github.com/masonTool/reflect