教妹學(xué) Java 第 39 講:反射
“二哥,什么是反射呀?”三妹開(kāi)門(mén)見(jiàn)山地問(wèn)。
“要想知道什么是反射,就需要先來(lái)了解什么是‘正射’。”我笑著對(duì)三妹說(shuō),“一般情況下,我們?cè)谑褂媚硞€(gè)類(lèi)之前已經(jīng)確定它到底是個(gè)什么類(lèi)了,拿到手就直接可以使用 new 關(guān)鍵字來(lái)調(diào)用構(gòu)造方法進(jìn)行初始化,之后使用這個(gè)類(lèi)的對(duì)象來(lái)進(jìn)行操作。”
Writer writer = new Writer();
writer.setName("沉默王二");
像上面這個(gè)例子,就可以理解為“正射”。而反射就意味著一開(kāi)始我們不知道要初始化的類(lèi)到底是什么,也就沒(méi)法直接使用 new 關(guān)鍵字創(chuàng)建對(duì)象了。
我們只知道這個(gè)類(lèi)的一些基本信息,就好像我們看電影的時(shí)候,為了抓住一個(gè)犯罪嫌疑人,警察就會(huì)問(wèn)一些目擊證人,根據(jù)這些證人提供的信息,找專(zhuān)家把犯罪嫌疑人的樣貌給畫(huà)出來(lái)——這個(gè)過(guò)程,就可以稱(chēng)之為反射。
嗯,不知道怎么回事,此刻我想起了古裝劇里城墻上貼的重金懸賞的畫(huà)面。
Class clazz = Class.forName("com.itwanger.s39.Writer");
Method method = clazz.getMethod("setName", String.class);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object,"沉默王二");
像上面這個(gè)例子,就可以理解為“反射”。
“反射的寫(xiě)法比正射復(fù)雜得多啊!”三妹感慨地說(shuō)。
“是的,反射的成本是要比正射的高得多。”我說(shuō),“反射的缺點(diǎn)主要有兩個(gè)。”
破壞封裝:由于反射允許訪(fǎng)問(wèn)私有字段和私有方法,所以可能會(huì)破壞封裝而導(dǎo)致安全問(wèn)題。 性能開(kāi)銷(xiāo):由于反射涉及到動(dòng)態(tài)解析,因此無(wú)法執(zhí)行 Java 虛擬機(jī)優(yōu)化,再加上反射的寫(xiě)法的確要復(fù)雜得多,所以性能要比“正射”差很多,在一些性能敏感的程序中應(yīng)該避免使用反射。
“那反射有哪些好處呢?”三妹問(wèn)。
反射的主要應(yīng)用場(chǎng)景有:
開(kāi)發(fā)通用框架:像 Spring,為了保持通用性,通過(guò)配置文件來(lái)加載不同的對(duì)象,調(diào)用不同的方法。 動(dòng)態(tài)代理:在面向切面編程中,需要攔截特定的方法,就會(huì)選擇動(dòng)態(tài)代理的方式,而動(dòng)態(tài)代理的底層技術(shù)就是反射。 注解:注解本身只是起到一個(gè)標(biāo)記符的作用,它需要利用發(fā)射機(jī)制,根據(jù)標(biāo)記符去執(zhí)行特定的行為。
“好了,來(lái)看一下完整的例子吧。”我對(duì)三妹說(shuō)。
Writer 類(lèi),有兩個(gè)字段,然后還有對(duì)應(yīng)的 getter/setter。
public class Writer {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
測(cè)試類(lèi):
public class ReflectionDemo1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Writer writer = new Writer();
writer.setName("沉默王二");
System.out.println(writer.getName());
Class clazz = Class.forName("com.itwanger.s39.Writer");
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
Method setNameMethod = clazz.getMethod("setName", String.class);
setNameMethod.invoke(object, "沉默王二");
Method getNameMethod = clazz.getMethod("getName");
System.out.println(getNameMethod.invoke(object));
}
}
來(lái)看一下輸出結(jié)果:
沉默王二
沉默王二
只不過(guò),反射的過(guò)程略顯曲折了一些。
第一步,獲取反射類(lèi)的 Class 對(duì)象:
Class clazz = Class.forName("com.itwanger.s39.Writer");
第二步,通過(guò) Class 對(duì)象獲取構(gòu)造方法 Constructor 對(duì)象:
Constructor constructor = clazz.getConstructor();
第三步,通過(guò) Constructor 對(duì)象初始化反射類(lèi)對(duì)象:
Object object = constructor.newInstance();
第四步,獲取要調(diào)用的方法的 Method 對(duì)象:
Method setNameMethod = clazz.getMethod("setName", String.class);
Method getNameMethod = clazz.getMethod("getName");
第五步,通過(guò) invoke() 方法執(zhí)行:
setNameMethod.invoke(object, "沉默王二");
getNameMethod.invoke(object)
“三妹,你看,經(jīng)過(guò)這五個(gè)步驟,基本上就掌握了反射的使用方法。”我說(shuō)。
“好像反射也沒(méi)什么復(fù)雜的啊!”三妹說(shuō)。
我先對(duì)三妹點(diǎn)點(diǎn)頭,然后說(shuō):“是的,掌握反射的基本使用方法確實(shí)不難,但要理解整個(gè)反射機(jī)制還是需要花一點(diǎn)時(shí)間去了解一下 Java 虛擬機(jī)的類(lèi)加載機(jī)制的。”
要想使用反射,首先需要獲得反射類(lèi)的 Class 對(duì)象,每一個(gè)類(lèi),不管它最終生成了多少個(gè)對(duì)象,這些對(duì)象只會(huì)對(duì)應(yīng)一個(gè) Class 對(duì)象,這個(gè) Class 對(duì)象是由 Java 虛擬機(jī)生成的,由它來(lái)獲悉整個(gè)類(lèi)的結(jié)構(gòu)信息。
也就是說(shuō),java.lang.Class 是所有反射 API 的入口。
而方法的反射調(diào)用,最終是由 Method 對(duì)象的 invoke() 方法完成的,來(lái)看一下源碼(JDK 8 環(huán)境下)。
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
兩個(gè)嵌套的 if 語(yǔ)句是用來(lái)進(jìn)行權(quán)限檢查的。
invoke() 方法實(shí)際上是委派給 MethodAccessor 接口來(lái)完成的。

MethodAccessor 接口有三個(gè)實(shí)現(xiàn)類(lèi),其中的 MethodAccessorImpl 是一個(gè)抽象類(lèi),另外兩個(gè)具體的實(shí)現(xiàn)類(lèi)繼承了這個(gè)抽象類(lèi)。

NativeMethodAccessorImpl:通過(guò)本地方法來(lái)實(shí)現(xiàn)反射調(diào)用; DelegatingMethodAccessorImpl:通過(guò)委派模式來(lái)實(shí)現(xiàn)反射調(diào)用;
通過(guò) debug 的方式進(jìn)入 invoke() 方法后,可以看到第一次反射調(diào)用會(huì)生成一個(gè)委派實(shí)現(xiàn) DelegatingMethodAccessorImpl,它在生成的時(shí)候會(huì)傳遞一個(gè)本地實(shí)現(xiàn) NativeMethodAccessorImpl。

也就是說(shuō),invoke() 方法在執(zhí)行的時(shí)候,會(huì)先調(diào)用 DelegatingMethodAccessorImpl,然后調(diào)用 NativeMethodAccessorImpl,最后再調(diào)用實(shí)際的方法。
“為什么不直接調(diào)用本地實(shí)現(xiàn)呢?”三妹問(wèn)。
“之所以采用委派實(shí)現(xiàn),是為了能夠在本地實(shí)現(xiàn)和動(dòng)態(tài)實(shí)現(xiàn)之間切換。動(dòng)態(tài)實(shí)現(xiàn)是另外一種反射調(diào)用機(jī)制,它是通過(guò)生成字節(jié)碼的形式來(lái)實(shí)現(xiàn)的。如果反射調(diào)用的次數(shù)比較多,動(dòng)態(tài)實(shí)現(xiàn)的效率就會(huì)更高,因?yàn)楸镜貙?shí)現(xiàn)需要經(jīng)過(guò) Java 到 C/C++ 再到 Java 之間的切換過(guò)程,而動(dòng)態(tài)實(shí)現(xiàn)不需要;但如果反射調(diào)用的次數(shù)比較少,反而本地實(shí)現(xiàn)更快一些。”我說(shuō)。
“那臨界點(diǎn)是多少呢?”三妹問(wèn)。
“默認(rèn)是 15 次。”我說(shuō),“可以通過(guò) -Dsun.reflect.inflationThreshold 參數(shù)類(lèi)調(diào)整。”
來(lái)看下面這個(gè)例子。
Method setAgeMethod = clazz.getMethod("setAge", int.class);
for (int i = 0;i < 20; i++) {
setAgeMethod.invoke(object, 18);
}
在 invoke() 方法處加斷點(diǎn)進(jìn)入 debug 模式,當(dāng) i = 15 的時(shí)候,也就是第 16 次執(zhí)行的時(shí)候,會(huì)進(jìn)入到 if 條件分支中,改變 DelegatingMethodAccessorImpl 的委派模式 delegate 為 (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(),而之前的委派模式 delegate 為 NativeMethodAccessorImpl。

“這下明白了吧?三妹。”我說(shuō),“接下來(lái),我們?cè)賮?lái)熟悉一下反射當(dāng)中常用的 API。”
1)獲取反射類(lèi)的 Class 對(duì)象
Class.forName(),參數(shù)為反射類(lèi)的完全限定名。
Class c1 = Class.forName("com.itwanger.s39.ReflectionDemo3");
System.out.println(c1.getCanonicalName());
Class c2 = Class.forName("[D");
System.out.println(c2.getCanonicalName());
Class c3 = Class.forName("[[Ljava.lang.String;");
System.out.println(c3.getCanonicalName());
來(lái)看一下輸出結(jié)果:
com.itwanger.s39.ReflectionDemo3
double[]
java.lang.String[][]
類(lèi)名 + .class,只適合在編譯前就知道操作的 Class。。
Class c1 = ReflectionDemo3.class;
System.out.println(c1.getCanonicalName());
Class c2 = String.class;
System.out.println(c2.getCanonicalName());
Class c3 = int[][][].class;
System.out.println(c3.getCanonicalName());
來(lái)看一下輸出結(jié)果:
com.itwanger.s39.ReflectionDemo3
java.lang.String
int[][][]
2)創(chuàng)建反射類(lèi)的對(duì)象
通過(guò)反射來(lái)創(chuàng)建對(duì)象的方式有兩種:
用 Class 對(duì)象的 newInstance()方法。用 Constructor 對(duì)象的 newInstance()方法。
Class c1 = Writer.class;
Writer writer = (Writer) c1.newInstance();
Class c2 = Class.forName("com.itwanger.s39.Writer");
Constructor constructor = c2.getConstructor();
Object object = constructor.newInstance();
3)獲取構(gòu)造方法
Class 對(duì)象提供了以下方法來(lái)獲取構(gòu)造方法 Constructor 對(duì)象:
getConstructor():返回反射類(lèi)的特定 public 構(gòu)造方法,可以傳遞參數(shù),參數(shù)為構(gòu)造方法參數(shù)對(duì)應(yīng) Class 對(duì)象;缺省的時(shí)候返回默認(rèn)構(gòu)造方法。getDeclaredConstructor():返回反射類(lèi)的特定構(gòu)造方法,不限定于 public 的。getConstructors():返回類(lèi)的所有 public 構(gòu)造方法。getDeclaredConstructors():返回類(lèi)的所有構(gòu)造方法,不限定于 public 的。
Class c2 = Class.forName("com.itwanger.s39.Writer");
Constructor constructor = c2.getConstructor();
Constructor[] constructors1 = String.class.getDeclaredConstructors();
for (Constructor c : constructors1) {
System.out.println(c);
}
4)獲取字段
大體上和獲取構(gòu)造方法類(lèi)似,把關(guān)鍵字 Constructor 換成 Field 即可。
Method setNameMethod = clazz.getMethod("setName", String.class);
Method getNameMethod = clazz.getMethod("getName");
5)獲取方法
大體上和獲取構(gòu)造方法類(lèi)似,把關(guān)鍵字 Constructor 換成 Method 即可。
Method[] methods1 = System.class.getDeclaredMethods();
Method[] methods2 = System.class.getMethods();
“注意,三妹,如果你想反射訪(fǎng)問(wèn)私有字段和(構(gòu)造)方法的話(huà),需要使用 Constructor/Field/Method.setAccessible(true) 來(lái)繞開(kāi) Java 語(yǔ)言的訪(fǎng)問(wèn)限制。”我說(shuō)。
“好的,二哥。還有資料可以參考嗎?”三妹問(wèn)。
“有的,有兩篇文章寫(xiě)得非常不錯(cuò),你在學(xué)習(xí)反射的時(shí)候可以作為參考。”我說(shuō)。
第一篇:深入理解 Java 反射和動(dòng)態(tài)代理
鏈接:https://dunwu.github.io/javacore/basics/java-reflection.html#_1-%E5%8F%8D%E5%B0%84%E7%AE%80%E4%BB%8B
第二篇:大白話(huà)說(shuō)Java反射:入門(mén)、使用、原理:
鏈接:https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html
不知不覺(jué),《教妹學(xué)Java》專(zhuān)欄已經(jīng)更新到第 39 講了,少說(shuō)應(yīng)該有 5 萬(wàn)字了吧?不由得往前翻了翻,看到之前有篇里面有個(gè) CS 小姐姐的留言,好溫暖!

來(lái)來(lái)來(lái),有沒(méi)有人要高度評(píng)價(jià)一下這個(gè)專(zhuān)欄?作為第 40 講更新的動(dòng)力?
??
