什么是反射
JAVA反射機(jī)制是在運(yùn)行狀態(tài)中装诡,對(duì)于任意一個(gè)類容握,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法信峻;這種動(dòng)態(tài)獲取的以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為Java的反射機(jī)制砌庄。
java反射機(jī)制提供的功能:
在運(yùn)行時(shí)判定任意一個(gè)對(duì)象所屬的類
在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對(duì)象
在運(yùn)行時(shí)判定任意一個(gè)類所具有的成員變量和方法
在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法
反射應(yīng)用場(chǎng)景
操作因訪問(wèn)權(quán)限限制的屬性和方法
如private屬性和方法舌厨,又如在android開(kāi)發(fā)中虐译,閱讀sdk源碼會(huì)發(fā)現(xiàn)有什么多方法加了hide注解狰挡,是google為了安全起見(jiàn)加的隱藏,應(yīng)用層是無(wú)法直接訪問(wèn)的淑仆,這時(shí)可以通過(guò)反射在運(yùn)行時(shí)調(diào)用(android 9.0 加了白名單機(jī)制涝婉,在黑名單的接口即使通過(guò)反射都無(wú)法調(diào)用)。
實(shí)現(xiàn)自定義注解
android有很多火爆的開(kāi)源框架都應(yīng)用了自定義注解和反射蔗怠,如EventBus墩弯,Retrofit;
動(dòng)態(tài)加載插件
很多大型App都用了動(dòng)態(tài)加載和熱修復(fù)的插件化技術(shù)寞射,也利用了反射的方式渔工,實(shí)現(xiàn)宿主(Host)對(duì)插件(LibPlugin)的調(diào)用,詳細(xì)可以了解插件化開(kāi)源框架桥温,如VirtualApp引矩,DroidPlugin,RePlugin
Java反射機(jī)制
反射涉及到四個(gè)核心類
java.lang.Class.java:類對(duì)象侵浸;
java.lang.reflect.Constructor.java:類的構(gòu)造器對(duì)象脓魏;
java.lang.reflect.Method.java:類的方法對(duì)象;
java.lang.reflect.Field.java:類的屬性對(duì)象通惫;
反射工作原理
要正確理解Java反射機(jī)制就得了解Class這個(gè)類,反射正是對(duì)Class類進(jìn)行操作混蔼。當(dāng)我們編寫好一個(gè)Java程序(Java文件)履腋,會(huì)先將其編譯(生成class文件),而運(yùn)行程序惭嚣,JVM會(huì)加載class文件到內(nèi)存遵湖,并產(chǎn)生一個(gè)Class對(duì)象,通過(guò)這個(gè)Class對(duì)象我們就能獲得加載到虛擬機(jī)當(dāng)中這個(gè)Class對(duì)象對(duì)應(yīng)的父類晚吞、接口延旧、方法、成員以及構(gòu)造方法的聲明和定義等信息槽地,我們通過(guò)new的形式創(chuàng)建對(duì)象實(shí)際上就是通過(guò)這些Class來(lái)創(chuàng)建迁沫。
反射的工作原理就是借助Class.java、Constructor.java捌蚊、Method.java集畅、Field.java這四個(gè)類在程序運(yùn)行時(shí)動(dòng)態(tài)訪問(wèn)和修改任何類的行為和狀態(tài)。
反射常用的API
1缅糟、獲取反射中的Class對(duì)象
在 Java API 中挺智,獲取 Class 類對(duì)象有三種方法:
第一種,使用 Class.forName 靜態(tài)方法窗宦。
當(dāng)你知道該類的全路徑名時(shí)赦颇,你可以使用該方法獲取 Class 類對(duì)象二鳄。
Class clz = Class.forName("java.lang.String");
第二種,使用 .class 方法媒怯。
這種方法只適合在編譯前就知道操作的 Class订讼。
Class clz = String.class;
第三種,使用類對(duì)象的 getClass() 方法沪摄。
String str = new String("Hello");
Class clz = str.getClass();
2躯嫉、通過(guò)反射創(chuàng)建類對(duì)象
通過(guò)反射創(chuàng)建類對(duì)象主要有兩種方式:通過(guò) Class 對(duì)象的 newInstance() 方法、通過(guò) Constructor 對(duì)象的 newInstance() 方法杨拐。
第一種:通過(guò) Class 對(duì)象的 newInstance() 方法祈餐。
Class clz = Stock.class;
Stock stock = (Stock)clz.newInstance();
第二種:通過(guò) Constructor 對(duì)象的 newInstance() 方法
Class clz = Stock.class;
Constructor constructor = clz.getConstructor();
Stock stock = (Stock)constructor.newInstance();
通過(guò) Constructor 對(duì)象創(chuàng)建類對(duì)象可以選擇特定構(gòu)造方法,而通過(guò) Class 對(duì)象則只能使用默認(rèn)的無(wú)參數(shù)構(gòu)造方法哄陶。下面的代碼就調(diào)用了一個(gè)有參數(shù)的構(gòu)造方法進(jìn)行了類對(duì)象的初始化帆阳。
Class clz = Stock.class;
Constructor constructor = clz.getConstructor(String.class, String.class,String.class);
Stock stock= (Stock)constructor.newInstance("000001", "平安銀行","sz");
3、通過(guò)反射獲取類屬性屋吨、方法蜒谤、構(gòu)造器
我們通過(guò) Class 對(duì)象的 getFields() 、getDeclaredFields()方法獲取屬性至扰,通過(guò)getMethod()鳍徽、getDeclaredMethod獲取方法。
帶的方法只能獲取類本身包括私有在內(nèi)的屬性或方法敢课,而不帶Declared的方法不能獲取私有屬性或方法阶祭,但包括父類及自身的所有公有屬性或方法。
Class clz = Stock.class;
Field[] fields = clz.getFields();//包括父類的所有公有屬性(不包括私有的)
Field[] declaredFields = clz.getDeclaredFields();//自身的公有直秆、私有屬性
Method[] methods = clz.getMethods();//包括父類的所有公有方法(不包括私有的)
Method[] declaredMethods = clz.getDeclaredMethods();//自身的公有濒募、私有方法
4、通過(guò)反射獲取屬性值及執(zhí)行方法
Class stockClass= Stock.class;
Object stockObject = stockClass.newInstance();
Field marketField = stockClass.getDeclaredField("market");
marketField.setAccessible(true);//私有屬性或方法圾结,必須設(shè)置accessible=true瑰剃,否則拋出IllegalAccessException異常
Log.i(TAG, "reflectPrivateField:" + marketField.get(stockObject));
Method method = stockClass.getDeclaredMethod("getTrend", String.class);
method.setAccessible(true);
Object trend = method.invoke(stockObject, "5");
Log.i(TAG, "reflectPrivateMethod:" + trend);
完整代碼示例
Prodct.java
public class Product {
private String mCode;
private String mName;
public String type;
public void setCode(String code) {
mCode = code;
}
public void setName(String name) {
mName = name;
}
public String getCode() {
return mCode;
}
public String getName() {
return mName;
}
public String toString() {
return "Product{code=" + mCode + ",name=" + mName + ",type=" + type + "}";
}
}
Stock.java
public class Stock extends Product {
private String market = "sz";
private int getTrend(String price) {
if (TextUtils.isEmpty(price)) {
return 0;
} else if (price.contains("-")) {
return -1;
} else {
return 1;
}
}
@Call("do_sth_call")
public void doSth() {
Log.i("Stock", "do sth call");
}
public String toString() {
return "Stock{code=" + getCode() + ",name=" + getName() + ",type=" + type + ",market=" + market + "}";
}
}
Call.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Call {
String value();
}
ReflectTest.java
public class ReflectTest {
public static final String TAG = "reflect";
public static void testReflect() {
reflectNewInstance();
reflectPrivateField();
reflectPrivateMethod();
reflectAnnotation();
reboot();
}
/**
* 創(chuàng)建對(duì)象
*/
public static void reflectNewInstance() {
try {
Class<?> stockClass = Class.forName("com.demo.reflect.Stock");
Object stockObject = stockClass.newInstance();
Product product = (Product) stockObject;
product.setCode("000001");
product.setName("平安銀行");
product.type = "1";
Log.i(TAG, "reflectNewInstance stock=" + product.toString());
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
/**
* 反射訪問(wèn)私有屬性
*/
public static void reflectPrivateField() {
try {
Class<?> stockClass = Class.forName("com.demo.reflect.Stock");
Object stockObject = stockClass.newInstance();
Field marketField = stockClass.getDeclaredField("market");
marketField.setAccessible(true);
Log.i(TAG, "reflectPrivateField:" + marketField.get(stockObject));
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
/**
* 反射訪問(wèn)私有方法
*/
public static void reflectPrivateMethod() {
try {
Class<?> stockClass = Class.forName("com.demo.reflect.Stock");
Object stockObject = stockClass.newInstance();
Method method = stockClass.getDeclaredMethod("getTrend", String.class);
method.setAccessible(true);
Object trend = method.invoke(stockObject, "5");
Log.i(TAG, "reflectPrivateMethod:" + trend);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
/**
* 反射調(diào)用注解方法
*/
public static void reflectAnnotation() {
try {
Class<?> stockClass = Class.forName("com.demo.reflect.Stock");
Object stockObject = stockClass.newInstance();
Method[] methods = stockClass.getMethods();
for (Method method : methods) {
Call call = method.getAnnotation(Call.class);
if (call != null && "do_sth_call".equals(call.value())) {
method.invoke(stockObject);
}
}
} catch (Exception e) {
}
}
// 重啟手機(jī)
public static void reboot() {
try {
Class<?> cServiceManager = Class.forName("android.os.ServiceManager");
Method mGetService = cServiceManager.getMethod("getService", String.class);
Object oPowerManagerService = mGetService.invoke(null, Context.POWER_SERVICE);
Class<?> cIPowerManagerStub = Class.forName("android.os.IPowerManager$Stub");
Method mReboot = cIPowerManagerStub.getMethod("reboot", boolean.class, String.class, boolean.class);
Method mAsInterface = cIPowerManagerStub.getMethod("asInterface", IBinder.class);
Object oIPowerManager = mAsInterface.invoke(null, oPowerManagerService);
mReboot.invoke(oIPowerManager, true, null, true);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
反射執(zhí)行reboot方法,會(huì)拋出異常筝野,java.lang.reflect.InvocationTargetException晌姚,異常原因是java.lang.SecurityException: Neither user 10547 nor current process has android.permission.REBOOT。原因是沒(méi)有reboot的操作權(quán)限歇竟。
Caused by: java.lang.SecurityException: Neither user 10547 nor current process has android.permission.REBOOT.
W/System.err: at android.os.IPowerManager$Stub$Proxy.reboot(IPowerManager.java:1596)
W/System.err: at com.android.server.power.PowerManagerService$BinderService.reboot(PowerManagerService.java:5662)
總結(jié)
本文總結(jié)了反射的作用及應(yīng)用場(chǎng)景舀凛,列舉了常用的API,并給出代碼示例途蒋。反射既有優(yōu)點(diǎn)也有缺點(diǎn)猛遍。
優(yōu)點(diǎn)是提供了靈活的機(jī)制,想對(duì)類做啥就做啥,一些黑科技就是用了反射實(shí)現(xiàn)了一般開(kāi)發(fā)無(wú)法做到的功能懊烤,另外很多框架應(yīng)用了反射使得開(kāi)發(fā)更加便利高效梯醒。
缺點(diǎn)是運(yùn)行時(shí)反射操作方法會(huì)比直接調(diào)用性能慢,取決于如何反射腌紧,一般可以忽略茸习,另外一個(gè)問(wèn)題就是安全性,隨意修改私有屬性和訪問(wèn)私有方法壁肋,破壞了類的封裝性号胚,可能潛在邏輯隱患,再一個(gè)是在安卓上應(yīng)用浸遗,可能出現(xiàn)適配問(wèn)題猫胁。因此應(yīng)用反射前要充分了解這些缺點(diǎn)帶來(lái)的影響。
參考
深入淺出反射
https://zhuanlan.zhihu.com/p/21423208
大白話說(shuō)Java反射:入門跛锌、使用弃秆、原理
https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html
java.lang.Class
java.lang.reflect.Method