前言
為什么要寫(xiě)Java的反射?因?yàn)楸救嗽陂喿x很多注入依賴(lài)這種開(kāi)源庫(kù)(類(lèi)似Dragger2,Butterknife)的源碼的時(shí)候,發(fā)現(xiàn)其代碼都運(yùn)用了大量的Java反射。本身Java反射就是框架的靈魂,為了能幫助更多的讀者讀懂這些開(kāi)源庫(kù)的代碼,我決定開(kāi)啟一個(gè)系列文章,分別是:Java的反射;Java的注解;利用Java的反射和注解手?jǐn)]一個(gè)Android注入依賴(lài)框架;ButterKnife源碼解析。這篇文章是該系列的第一篇:Java的反射。
什么是Java的反射機(jī)制
java允許開(kāi)發(fā)者在程序運(yùn)行過(guò)程中操作(訪問(wèn)和修改)類(lèi)的各種屬性以及方法。注意加粗的幾個(gè)字,“程序運(yùn)行過(guò)程中”,那什么是Java文件的程序運(yùn)行過(guò)程呢?
Java文件的程序運(yùn)行過(guò)程
首先Java文件的程序運(yùn)行過(guò)程分為三個(gè)階段:
- Source(源代碼階段)
- Class(類(lèi)對(duì)象階段)
- Runtime(運(yùn)行時(shí))
Source(源代碼階段)
我們先創(chuàng)建一個(gè)Person.java文件,再用javac命令編譯Person.java文件,接著就會(huì)生成一個(gè)Person.class文件,此時(shí)這兩個(gè)文件都在磁盤(pán)里面,還有被加載到JVM內(nèi)存里面,這個(gè)時(shí)候就處于Source(源代碼階段)

Class(類(lèi)對(duì)象階段)
當(dāng)Person.class文件被類(lèi)加載器(ClassLoader)加載到JVM的內(nèi)存里,此時(shí)JVM運(yùn)行時(shí)的方法區(qū)里面會(huì)生成一個(gè)Class類(lèi)對(duì)象Class<Person> clz,這個(gè)Class類(lèi)對(duì)象非常重要,這里面包含了我們對(duì)類(lèi)的描述。比如說(shuō)我們現(xiàn)在這個(gè)Person.Java文件里有成員變量、構(gòu)造方法以及成員方法
public class Person extends Object {
/**
* 成員變量
* */
public String name;
int age;
protected int sex;
private int id;
/**
* 構(gòu)造方法
* */
public Person(){
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 成員方法
* */
private void eat(){
System.out.println("eat-----");
}
private void drink(String drink) {
System.out.println("drink----"+drink);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
", id=" + id +
'}';
}
}
那么Class類(lèi)對(duì)象是如何描述這個(gè)Person類(lèi)呢?Java的思想是一切皆對(duì)象,Class類(lèi)對(duì)象對(duì)類(lèi)的描述也是通過(guò)對(duì)象。
- 成員變量:用的是Field對(duì)象
- 構(gòu)造方法:用的是Constructor對(duì)象
- 成員方法:用的是Method對(duì)象
生成Class類(lèi)對(duì)象存在JVM內(nèi)存中的這個(gè)階段就是Class類(lèi)對(duì)象階段
Runtime(運(yùn)行時(shí))
這個(gè)階段可能大家相對(duì)就比較了解了,因?yàn)槲覀僯ava程序絕大多數(shù)的情況都處于這個(gè)階段,舉個(gè)例子,當(dāng)我們調(diào)用Person person = new Person();創(chuàng)建一個(gè)對(duì)象時(shí),接著會(huì)在堆內(nèi)存里生成一個(gè)person對(duì)象,但是這個(gè)person到底是如何被生成的呢?其實(shí)還是調(diào)用Class類(lèi)對(duì)象的方法,可見(jiàn)Class類(lèi)對(duì)象是多么重要的,而這個(gè)Class類(lèi)對(duì)象也是我們實(shí)現(xiàn)反射的基礎(chǔ)。
小結(jié)
這里做一個(gè)小結(jié),因?yàn)閖ava運(yùn)行過(guò)程中這三個(gè)階段對(duì)我們理解反射的概念非常重要,我這里畫(huà)了一個(gè)圖,大家理解一下

獲取Class類(lèi)對(duì)象
上面已經(jīng)講到了Class類(lèi)對(duì)象是我們反射的基礎(chǔ),有了Class類(lèi)對(duì)象才能實(shí)現(xiàn)反射,那該如何獲取Class類(lèi)對(duì)象呢?
java給我們提供了三種方式獲取Class類(lèi)對(duì)象,同時(shí)也對(duì)應(yīng)上面講的三個(gè)階段:
- Source(源代碼階段):Class clz = Class.forName("com.example.kaka.Person");
,這個(gè)方法就是通過(guò)Java文件的全限定名(包名+類(lèi)名)把它的class文件加載到JVM內(nèi)存里面,此時(shí)我們就能得到Class類(lèi)文件,由于是源代碼階段,所以這個(gè)方法我們經(jīng)常用來(lái)加載配置文件,比如說(shuō)Spring在啟動(dòng)的時(shí)候會(huì)加載很多的配置文件,底層實(shí)現(xiàn)用的就是這個(gè)方法 - Class類(lèi)對(duì)象:Class clz = Person.class;這個(gè)就直接用的Person的靜態(tài)屬性
- Runtime(運(yùn)行時(shí)):因?yàn)槭沁\(yùn)行時(shí)了,首先我們得先有Person這個(gè)對(duì)象,Pserson p = new Person;Class clz = p.getClass();
注意:通過(guò)以上三種方式生成的Class類(lèi)對(duì)象都是相同的,也就是在內(nèi)存的地址是一樣的,這里如果你明白類(lèi)的加載機(jī)制應(yīng)該很容易理解,不明白的可以溫習(xí)一下類(lèi)的加載機(jī)制。
Class類(lèi)對(duì)象能干什么
Class類(lèi)對(duì)象是對(duì)類(lèi)的描述,拿到了所有類(lèi)的信息,理論上我們就什么都能干,比如說(shuō):獲取類(lèi)的構(gòu)造方法、成員變量、成員方法、類(lèi)名等等...下面我們分別講一下
獲取類(lèi)的構(gòu)造方法
對(duì)類(lèi)的構(gòu)造方法修飾的對(duì)象是Constructor對(duì)象,Class類(lèi)對(duì)象里面提供了5個(gè)相關(guān)方法
- public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
- public Constructor<?>[] getConstructors() throws SecurityException
- public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException - public Constructor<?>[] getDeclaredConstructors() throws SecurityException
- public Constructor<?> getEnclosingConstructor()
先聊前四個(gè)方法,前兩個(gè)方法的方法名沒(méi)有Declared修飾,他們只能獲取到用public修飾的構(gòu)造方法;后面兩個(gè)方法有有Declared修飾,他們可以獲取所有的構(gòu)造方法,不管是private還是protected修飾的,這里我們寫(xiě)一段代碼,打印一下log
這里為了演示效果,我在Person類(lèi)里多加了幾個(gè)構(gòu)造方法
/**
* 構(gòu)造方法
*/
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private Person(int id) {
this.id = id;
}
protected Person(int id, int sex) {
this.id = id;
this.sex = sex;
}
打印log的代碼
Class<Person> personClass = Person.class;
System.out.println("-----------------所有public修飾的構(gòu)造方法-------------------");
Constructor<?>[] constructors = personClass.getConstructors();
for (Constructor constructor1:constructors) {
System.out.println(constructor1);
}
System.out.println("---------------所有的構(gòu)造方法---------------------");
Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
for (Constructor declaredConstructor:declaredConstructors) {
System.out.println(declaredConstructor);
}
看一下log的打印結(jié)果

我們?cè)倏从袇?shù)的那兩個(gè)方法,他們是獲取指定方法名的構(gòu)造方法,接收的參數(shù)是參數(shù)類(lèi)型的Class類(lèi)對(duì)象;而無(wú)參的兩個(gè)方法后面帶了個(gè)s,也就是他們獲取的是構(gòu)造方法方法數(shù)組。
Class<Person> personClass = Person.class;
Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
System.out.println("----------------指定參數(shù)類(lèi)型的public修飾的構(gòu)造方法-----------------");
System.out.println(constructor);
personClass.getDeclaredConstructor();
constructor = personClass.getDeclaredConstructor(int.class);
System.out.println("----------------指定參數(shù)類(lèi)型的構(gòu)造方法-----------------");
System.out.println(constructor);
personClass.getDeclaredConstructor();
運(yùn)行結(jié)果

現(xiàn)在再講一下第5個(gè)方法,getEnclosingConstructor(),官方的解釋?zhuān)骸叭绻?Class 對(duì)象表示構(gòu)造方法中的一個(gè)本地或匿名類(lèi),則返回 Constructor 對(duì)象,它表示底層類(lèi)的立即封閉構(gòu)造方法。否則返回 null。簡(jiǎn)單來(lái)說(shuō),就是Person里面的構(gòu)造方法聲明了一個(gè)內(nèi)部類(lèi)InnerMan,此時(shí)用InnerMan的Class類(lèi)對(duì)象調(diào)用getEnclosingConstructor()獲取到的構(gòu)造方法是Person的構(gòu)造方法
public Person() {
class InnerMan {
}
InnerMan man = new InnerMan();
Constructor<?> enclosingConstructor = man.getClass().getEnclosingConstructor();
System.out.println(enclosingConstructor);
}

這個(gè)方法在實(shí)際應(yīng)用中比較少見(jiàn),大家知道即可
用過(guò)上面的方式我們拿到了類(lèi)的構(gòu)造方法對(duì)象Constructor,那我們現(xiàn)在要干嘛?反射機(jī)制的定義是Java允許開(kāi)發(fā)者在程序運(yùn)行過(guò)程中**操作(訪問(wèn)和修改)類(lèi)的各種屬性以及方法?,F(xiàn)在拿到Constructor,我們要做的操作就是創(chuàng)建對(duì)象。Constructor提供一個(gè)非常重要的api
- public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
調(diào)用這個(gè)方法我們就能得到一個(gè)對(duì)象,里面接收的是一個(gè)可變參數(shù),如果調(diào)用無(wú)參的構(gòu)造方法就什么都不傳,有參的就傳對(duì)應(yīng)的參數(shù)就行
Class<Person> personClass = Person.class;
Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
Person person1 = constructor.newInstance("張三", 24);
constructor = personClass.getConstructor();
Person person2 = constructor.newInstance();
這樣我們就能創(chuàng)建Person對(duì)象了。我們?cè)僭囈幌聀rivate修飾的構(gòu)造方法,都知道private修飾的構(gòu)造方法在類(lèi)的外部是不允許被調(diào)用的,但是對(duì)反射而言不區(qū)分什么內(nèi)部外部,反射都可以得到,就是這么強(qiáng)大
constructor = personClass.getDeclaredConstructor(int.class);
constructor.setAccessible(true);
Person person = constructor.newInstance(1);
注意:這里最重要的一句是constructor.setAccessible(true);,當(dāng)我們?cè)L問(wèn)private修飾的構(gòu)造方法、成員變量、成員方法,都需要調(diào)用這個(gè)API,它的意思是取消安全檢查機(jī)制,這樣我們就可以調(diào)用私有的構(gòu)造方法了,這一點(diǎn)非常重要,假如我們不加這一句代碼會(huì)怎么樣,我們?cè)囋?br>

直接就報(bào)錯(cuò)了,所以在訪問(wèn)私有屬性以及方法之前,必須要加上obj.setAccessible(true);
獲取類(lèi)的成員變量
之前講過(guò)對(duì)成員變量修飾的對(duì)象是Field,Class類(lèi)對(duì)象里面提供了四個(gè)相關(guān)的方法
- public Field getField(String name) throws NoSuchFieldException
- public Field[] getFields() throws SecurityException
- public native Field getDeclaredField(String name) throws NoSuchFieldException
- public native Field[] getDeclaredFields();
跟前面的構(gòu)造方法類(lèi)似,首先看前兩個(gè)方法的方法名沒(méi)有Declared修飾,他們只能獲取到用public修飾的成員變量;后面兩個(gè)方法有有Declared修飾,他們可以獲取所有的成員變量,不管是private還是protected修飾的;我們?cè)倏从袇?shù)的那兩個(gè)方法,他們是獲取指定變量名的成員變量,而無(wú)參的兩個(gè)方法后面帶了個(gè)s,也就是他們獲取的是變量數(shù)組。
Class<Person> personClass = Person.class;
Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
Person person1 = constructor.newInstance("張三", 24);
Field[] declaredFields = personClass.getDeclaredFields();//獲取所有成員變量
Field id = personClass.getDeclaredField("id");//獲取指定的成員變量
Field name = personClass.getField("name");//獲取指定的public成員變量
現(xiàn)在我們拿到了成員變量了,對(duì)成員變量而言,我們的操作就是讀取和修改,F(xiàn)ield對(duì)象也提供了兩個(gè)重要的api
- public native Object get(Object obj) throws IllegalArgumentException, IllegalAccessException;//獲取變量的值
- public native void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException;//設(shè)置變量的值,第一個(gè)參數(shù)是對(duì)象,第二個(gè)參數(shù)是設(shè)置變量的值
Class<Person> personClass = Person.class;
Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
Person person1 = constructor.newInstance("張三", 24);
// Field[] declaredFields = personClass.getDeclaredFields();//獲取所有成員變量
// Field id = personClass.getDeclaredField("id");
Field name = personClass.getField("name");
Object nameVal = name.get(person1);
System.out.println(nameVal);
name.set(person1,"李四");
nameVal = name.get(person1);
System.out.println(nameVal);
這里解釋一下,我們以name為例,先獲取一下name的值,然后在把name改成“李四”

控制臺(tái)打印的結(jié)果符合我們的預(yù)期,同樣地,當(dāng)我們?cè)L問(wèn)被private所修飾的成員變量也要調(diào)用obj.setAccessible(true);
Field id = personClass.getDeclaredField("id");
id.setAccessible(true);
id.set(person1,3);
//這里就不做演示了
獲取類(lèi)的成員方法
描述方法的對(duì)象是Method,Class類(lèi)對(duì)象給我們提供了5個(gè)獲取Method的相關(guān)方法
- public Method[] getMethods() throws SecurityException:獲取所有public修飾的方法
- public Method getMethod(String name, Class<?>... parameterTypes):獲取指定方法名以及參數(shù)類(lèi)型的被public修飾的成員方法
- public Method[] getDeclaredMethods() throws SecurityException:獲取所有的成員方法
- public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException:獲取指定方法名以及參數(shù)類(lèi)型的成員方法 - public Method getEnclosingMethod():如果該 Class 對(duì)象表示成員方法中的一個(gè)本地或匿名類(lèi),則返回 Method 對(duì)象,它表示底層類(lèi)的立即封閉成員方法。否則返回 null?;旧细懊鎔etEnclosingConstructor()是類(lèi)似的。
Method[] methods = personClass.getMethods();//獲取所有的public方法
Method[] declaredMethods = personClass.getDeclaredMethods();//獲取所有的方法
Method drink = personClass.getDeclaredMethod("drink", String.class);//獲取指定的方法名和參數(shù)類(lèi)型的方法
拿到了方法對(duì)象Method以后,我們還是要操作,只需要調(diào)用方法就行了,對(duì)應(yīng)的Method提供了一個(gè)重要的api
- public native Object **invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException:第一個(gè)參數(shù)傳具體對(duì)象,第二個(gè)傳方法里的參數(shù),沒(méi)有就不傳
Method drink = personClass.getDeclaredMethod("drink", String.class);
drink.setAccessible(true);
drink.invoke(person1,"可樂(lè)");
訪問(wèn)私有方法同樣要調(diào)用** drink.setAccessible(true);**
注意:關(guān)于Class對(duì)象還有其他一些比較常用的api,這里我們就不單獨(dú)羅列出來(lái)了,直接放到下面:
- public String getName():獲取類(lèi)的全限定名
- public Package getPackage():獲取Package對(duì)象
- public Class<?>[] getInterfaces() :獲取接口數(shù)組
- public Annotation[] getAnnotations():獲取注解
至此,我們就通過(guò)反射的機(jī)制拿到了一個(gè)類(lèi)的所有信息,并可以操作其相關(guān)信息,建議大家自己寫(xiě)個(gè)小demo,跑一下
總結(jié)
反射機(jī)制可以允許我們?cè)趈ava程序運(yùn)行過(guò)程中操作類(lèi)的各種屬性。java程序運(yùn)行過(guò)程又分為三個(gè)階段,Source(源代碼);Class(類(lèi)對(duì)象);Runtime(運(yùn)行時(shí)),這三個(gè)階段又同時(shí)提供了獲取Class類(lèi)對(duì)象的方法,拿到了Class對(duì)象以后我們就可以操作類(lèi)的屬性(成員變量、構(gòu)造方法、成員方法),以此來(lái)實(shí)現(xiàn)了反射的效果。反射很強(qiáng)大,它允許我們?cè)谌魏蔚胤讲僮黝?lèi)的任何信息,因此在性能略微有影響,不過(guò)影響不大,因?yàn)橛布矫娴脑颍?0ms和70ms對(duì)我們來(lái)說(shuō)是幾乎沒(méi)有差距。反射也是Java框架的靈魂,掌握反射有助于我們理解使用的框架,更能幫助我們開(kāi)發(fā)自己的框架。