反射的作用
開(kāi)門(mén)見(jiàn)山地說(shuō)說(shuō)反射的作用
1.為我們提供了全面的分析類(lèi)信息的能力
2.動(dòng)態(tài)加載類(lèi)
我理解的“反射”的意義
我理解的java反射機(jī)制就是: 提供一套完善而強(qiáng)大的API“反射“類(lèi)的結(jié)構(gòu)。
打個(gè)比方,反射機(jī)制就像是一面鏡子,而類(lèi)就像是一個(gè)在照著鏡子的人。
鏡子(反射機(jī)制)照出(反射)了人的全貌(類(lèi)的全方位的信息,例如方法,成員變量和構(gòu)造器等的相關(guān)信息)
為什么要照鏡子? 因?yàn)椴徽甄R子看不清楚自己的全貌,“鏡子”就是為了解決這個(gè)問(wèn)題出現(xiàn)的(為我們提供全面分析類(lèi)的能力)
好吧,我知道這聽(tīng)起來(lái)還是很模糊,讓我們一步一步來(lái):
類(lèi)也是對(duì)象
在java里有一句話(huà):萬(wàn)物皆對(duì)象, 即使是int等基本類(lèi)型,雖然本質(zhì)上不是對(duì)象,但行為卻也和對(duì)象密切相關(guān)(基本包裝類(lèi)型和自動(dòng)裝箱)
所以有一個(gè)可能完全打破我們常規(guī)思維的論斷是: 類(lèi)也是對(duì)象
“類(lèi)”對(duì)象和“類(lèi)”類(lèi)型
好吧,其實(shí)說(shuō)“ 類(lèi)也是對(duì)象”并不太好,而應(yīng)該說(shuō),java中每個(gè)類(lèi)都有一個(gè)與之對(duì)應(yīng)的“類(lèi)”對(duì)象(Class對(duì)象),這個(gè)“類(lèi)”對(duì)象由jvm生成,并保存了對(duì)應(yīng)類(lèi)的相關(guān)信息。例如,假設(shè)我們的java文件涉及三個(gè)類(lèi):a類(lèi),b類(lèi)和c類(lèi),那么編譯的時(shí)候就會(huì)對(duì)應(yīng)生成a類(lèi)的“類(lèi)”對(duì)象,a類(lèi)的“類(lèi)”對(duì)象,a類(lèi)的“類(lèi)”對(duì)象,分別用于保存和a,b,c類(lèi)對(duì)應(yīng)的信息
我們的思維是這樣的: 一個(gè)對(duì)象必然有一個(gè)與之對(duì)應(yīng)的類(lèi),因?yàn)橹挥蓄?lèi)才能實(shí)例化對(duì)象啊
那么,“類(lèi)對(duì)象”的“上面”,應(yīng)該還有一個(gè)類(lèi)才對(duì)!這個(gè)“類(lèi)之上的類(lèi)”,就是java.lang.Class,它是所有“類(lèi)”對(duì)象的類(lèi)(這樣說(shuō)可能聽(tīng)起來(lái)很拗口)
我們這樣聲明一個(gè)“類(lèi)”對(duì)象,假設(shè)這個(gè)類(lèi)對(duì)象(Class對(duì)象)是a——Class a
我們稱(chēng)a屬于“類(lèi)”類(lèi)型(Class類(lèi)型)
所以我們可以其實(shí)可以將java中的對(duì)象分為兩種:
- 實(shí)例對(duì)象
- Class對(duì)象
所以我們今天要講的第一個(gè)內(nèi)容是: 有別于平時(shí)使用的實(shí)例對(duì)象的——Class對(duì)象
取得Class對(duì)象的三種方式
我們假設(shè)有這么一個(gè)類(lèi)叫MyClass:
public class MyClass { }
那么取得該類(lèi)對(duì)應(yīng)Class對(duì)象的方法有三種:
一. 通過(guò)“類(lèi)名.class”的方式取得
Class classInstance= MyClass.class;
二. 通過(guò)類(lèi)創(chuàng)建的實(shí)例對(duì)象的getClass方法取得
MyClass myClass = new MyClass();
Class classInstance = myClass.getClass();
三.通過(guò)Class類(lèi)的靜態(tài)方法forName方法取得(參數(shù)是帶包名的完整的類(lèi)名)
Class classInstance = Class.forName("mypackage.MyClass");
【注意】
1.運(yùn)行forName時(shí)候可能會(huì)因?yàn)檎也坏桨鴴伋鲆褭z查異常ClassNotFoundException,所以我們需要將其包裹在try-catch語(yǔ)句中:
try {
Class classInstance = Class.forName("mypackage.MyClass");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
2.上面三種方法取得的對(duì)象都是相同的,所以效果上等價(jià)
利用反射API全面分析類(lèi)的信息——方法,成員變量,構(gòu)造器
我們上面提到,反射的一大作用是用于分析類(lèi)的結(jié)構(gòu),或者說(shuō)用于分析和這個(gè)類(lèi)有關(guān)的所有信息。而這些信息就是類(lèi)的基本的組成: 方法,成員變量和構(gòu)造器
事實(shí)上,和我們上面所介紹的Class類(lèi)和Class對(duì)象相似的是,一個(gè)類(lèi)中的方法,成員變量和構(gòu)造器也分別對(duì)應(yīng)著一個(gè)對(duì)象
1.每個(gè)方法都對(duì)應(yīng)有一個(gè)保存和該方法有關(guān)信息的Method對(duì)象, 這個(gè)對(duì)象所屬的類(lèi)是
java.lang.reflect.Method;
2.每個(gè)成員變量都對(duì)應(yīng)有一個(gè)保存和該變量有關(guān)信息的Field對(duì)象,這個(gè)對(duì)象所屬的類(lèi)是
java.lang.reflect.Field
- 每個(gè)構(gòu)造器都對(duì)應(yīng)有一個(gè)保存和該構(gòu)造器有關(guān)信息的Constructor對(duì)象,這個(gè)對(duì)象所屬的類(lèi)是
java.lang.reflect.Constructor
方法,成員變量和構(gòu)造器是附屬于某一個(gè)類(lèi)的,正因如此,我們應(yīng)該先取得某一個(gè)類(lèi)對(duì)應(yīng)的Class對(duì)象,其次才考慮如何取得 Method/Field/Constructor對(duì)象
我們可以通過(guò)一系列的方法,從一個(gè)類(lèi)的Class對(duì)象中取得對(duì)應(yīng)的Method對(duì)象,F(xiàn)ield對(duì)象和Constructor對(duì)象
假設(shè)c是一個(gè)類(lèi)的Class對(duì)象:
通過(guò) c.getDeclaredMethods()可取得這個(gè)類(lèi)中所有聲明方法對(duì)應(yīng)的Method對(duì)象組成的數(shù)組
通過(guò) c.getDeclaredFields()可取得這個(gè)類(lèi)中所有聲明的成員變量對(duì)應(yīng)的Field對(duì)象組成的數(shù)組
通過(guò) c.getConstructors(); 可取得這個(gè)類(lèi)中所有構(gòu)造函數(shù)所對(duì)應(yīng)的Constructor對(duì)象所組成的數(shù)組
在下面的示例中,我們將遍歷某一個(gè)類(lèi)中方法,成員變量和構(gòu)造器的名稱(chēng):
MyClass.java:
public class MyClass {
private int value; //成員變量
public MyClass (int value) { this.value = value; } //構(gòu)造函數(shù)
public int getValue() { return value; } //方法1
public void setValue(int value) { this.value = value; } //方法2
}
Test.java:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test {
public static void printClassMessage (Object obj) {
Class c = obj.getClass(); // 獲取obj所屬類(lèi)的Class對(duì)象
Method [] methods = c.getDeclaredMethods(); // 獲取方法對(duì)象列表
System.out.println("遍歷MyClass類(lèi)里的所有方法的名稱(chēng):");
for(int i =0; i<methods.length; i++) {
System.out.println(methods[i].getName());
}
Field [] fields = c.getDeclaredFields(); // 獲取成員變量對(duì)象列表
System.out.println("遍歷MyClass類(lèi)里的所有成員變量的名稱(chēng):");
for(int i =0; i<fields.length; i++) {
System.out.println(fields[i].getName());
}
Constructor [] constructors = c.getConstructors(); // 獲取構(gòu)造函數(shù)對(duì)象列表
System.out.println("遍歷MyClass類(lèi)里的所有構(gòu)造函數(shù)的名稱(chēng):");
for(int i =0; i<constructors.length; i++) {
System.out.println(constructors[i].getName());
}
}
public static void main(String [] args) {
MyClass myClass = new MyClass(1); // 創(chuàng)建一個(gè)MyClass對(duì)象
printClassMessage(myClass); // 打印這個(gè)對(duì)象所屬類(lèi)的相關(guān)信息
}
}
運(yùn)行結(jié)果:
遍歷MyClass類(lèi)里的所有方法的名稱(chēng):
getValue
setValue
遍歷MyClass類(lèi)里的所有成員變量的名稱(chēng):
value
遍歷MyClass類(lèi)里的所有構(gòu)造函數(shù)的名稱(chēng):
mypackage.MyClass
上面的例子僅僅是作為一個(gè)展示,Method/Field/Constructor對(duì)象的API當(dāng)然不僅限于getName這樣獲取名稱(chēng)的簡(jiǎn)單操作,所以接下來(lái)我將分別介紹更具體的反射API
利用反射API分析類(lèi)中方法信息
getMethods和getDeclaredMethods方法
getMethods和getDeclaredMethods的區(qū)別在于:
getMethods取得的method對(duì)應(yīng)的方法包括從父類(lèi)中繼承的那一部分,而
getDeclaredMethods取得的method對(duì)應(yīng)的方法不包括從父類(lèi)中繼承的那一部分
例如上面通過(guò)打印getDeclaredMethods打印的MyClass的方法信息:
getValue
setValue
讓我們看看通過(guò)getMethods打印又會(huì)取得什么結(jié)果:
Test.java:
import java.lang.reflect.Method;
public class Test {
public static void printMethodsMessage (Object obj) {
Class c = obj.getClass();
Method [] methods = c.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
}
public static void main(String [] args) {
MyClass myClass = new MyClass(1);
printMethodsMessage(myClass);
}
}
運(yùn)行結(jié)果:
getValue
setValue
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
讓我們思考一下: 一個(gè)方法有哪些“信息”值得(或需要)我們?nèi)シ治瞿兀?/p>
主要是兩部分:
- 返回值
- 方法參數(shù)
你可能猜的出來(lái), Method對(duì)象已經(jīng)提供了一套API去獲取這些信息了,讓我們來(lái)看看:
通過(guò)method.getReturnType()獲取方法返回值對(duì)應(yīng)的Class對(duì)象
import java.lang.reflect.Method;
public class Test {
public static void printMethodsMessage (Object obj) {
Class c = obj.getClass(); // 取得obj所屬類(lèi)對(duì)應(yīng)的Class對(duì)象
Method [] methods = c.getDeclaredMethods(); // 取得obj所屬類(lèi)中方法對(duì)應(yīng)的Method對(duì)象組成的數(shù)組
for (Method method : methods) { // 遍歷Method對(duì)象
String name = method.getName(); // 取得方法名
Class returnClass = method.getReturnType(); // 獲取方法返回值對(duì)應(yīng)的Class對(duì)象
String returnName = returnClass.getName(); //獲取返回值所屬類(lèi)的類(lèi)名——也即返回值類(lèi)型
System.out.println(name + "方法的返回值類(lèi)型是" + returnName);
}
}
public static void main(String [] args) {
MyClass myClass = new MyClass(1);
printMethodsMessage(myClass);
}
}
運(yùn)行結(jié)果:
getValue方法的返回值類(lèi)型是int
setValue方法的返回值類(lèi)型是void
通過(guò)method.getReturnType(),我們?nèi)〉昧嗽摲椒ǖ姆祷刂祵?duì)應(yīng)的Class對(duì)象(哈哈,繞了一圈后還是回到Class對(duì)象上了)
然后通過(guò)Class對(duì)象調(diào)用getName方法就取得了返回值所屬的類(lèi)的名稱(chēng),也即返回值類(lèi)型
通過(guò)method.getParameterTypes()獲取方法各參數(shù)的Class對(duì)象組成的數(shù)組
MyClass.java:
public class MyClass {
public void method1 (int a, long b) { };
public void method2 (float a, double b) { };
public void method3 (String str) { };
}
Test.java:
public class Test {
public static void printMethodsMessage (Object obj) {
Class c = obj.getClass(); // 取得obj所屬類(lèi)對(duì)應(yīng)的Class對(duì)象
Method [] methods = c.getDeclaredMethods(); // 取得obj所屬類(lèi)中方法對(duì)應(yīng)的Method對(duì)象組成的數(shù)組
for (Method method : methods) { // 遍歷Method對(duì)象
String methodName = method.getName(); // 取得方法名
String paramsStr = ""; // 用于存放某個(gè)方法參數(shù)類(lèi)型列表的字符串
Class [] paramsClasses = method.getParameterTypes();
for (Class pc: paramsClasses) {
String paramStr = pc.getName(); // 獲取當(dāng)前參數(shù)類(lèi)型
paramsStr+=paramStr + " ";
}
System.out.println(methodName+ "方法的所有參數(shù)的類(lèi)型列表:" + paramsStr);
}
}
public static void main(String [] args) {
MyClass myClass = new MyClass();
printMethodsMessage(myClass);
}
}
運(yùn)行結(jié)果:
method2方法的參數(shù)類(lèi)型列表:float double
method1方法的參數(shù)類(lèi)型列表:int long
method3方法的參數(shù)類(lèi)型列表:java.lang.String
利用反射API分析類(lèi)中成員變量信息
獲取成員變量類(lèi)型對(duì)應(yīng)的的Class對(duì)象
讀取成員變量的值
MyClass.java:
public class MyClass {
private int number = 123;
private String name ="彭湖灣";
}
Test.java:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test {
public static void printFieldsMessage (Object obj) {
Class c = obj.getClass(); // 取得obj所屬類(lèi)對(duì)應(yīng)的Class對(duì)象
try {
Field field = c.getDeclaredField("name"); // 取得名稱(chēng)為name的field對(duì)象
field.setAccessible(true); // 這一步很重要?。?!設(shè)置為true才能訪(fǎng)問(wèn)私有成員變量name的值!
String nameValue = (String) field.get(obj); // 獲取obj中name成員變量的值
System.out.println("MyClass類(lèi)中name成員變量的值為:" + nameValue); // 輸出
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String [] args) {
MyClass myClass = new MyClass();
printFieldsMessage(myClass);
}
}
運(yùn)行結(jié)果:
MyClass類(lèi)中name成員變量的值為:彭湖灣
通過(guò)getType方法讀取成員變量類(lèi)型的Class對(duì)象
這里只展現(xiàn)比較關(guān)鍵的兩段代碼:
Field field = class1.getDeclaredField(number");
System.out.print(field.getType().getName());
運(yùn)行結(jié)果:
int
【注意】
因?yàn)閖ava權(quán)限的原因,直接讀取私有成員變量的值是非法的(加了field.setAccessible(true)后就可以了),但仍可以直接讀取私有成員變量的類(lèi)型
利用反射API分析類(lèi)中構(gòu)造器信息
分析構(gòu)造函數(shù)的時(shí)候,其實(shí)思路大體上和分析方法時(shí)候一致,關(guān)鍵在于獲取參數(shù)所屬類(lèi)的Class對(duì)象
區(qū)別在于:
- 獲取該類(lèi)聲明的構(gòu)造器用的是getDeclaredConstructors方法而不是getDeclaredMethods方法
- 構(gòu)造函數(shù)沒(méi)有返回值,所以不需要分析返回值(我似乎說(shuō)了廢話(huà)....)
廢話(huà)不多說(shuō)了,看下面的例子:
MyClass.java:
public class MyClass {
public MyClass(int a, String str){}
}
Test.java:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test {
public static void printContructorsMessage (Object obj) {
Class c = obj.getClass(); // 取得obj所屬類(lèi)對(duì)應(yīng)的Class對(duì)象
Constructor [] constructors = c.getDeclaredConstructors();
for (Constructor constructor : constructors) {
Class [] paramsClasses = constructor.getParameterTypes();
String paramsStr = "";
for (Class pc : paramsClasses) {
String paramStr = pc.getName();
paramsStr+=paramStr + " ";
}
System.out.println("構(gòu)造函數(shù)的所有參數(shù)的類(lèi)型列表:" + paramsStr);
}
}
public static void main(String [] args) {
MyClass myClass = new MyClass(1, "彭湖灣");
printContructorsMessage(myClass);
}
}
運(yùn)行結(jié)果:
構(gòu)造函數(shù)的所有參數(shù)的類(lèi)型列表:int java.lang.String
利用反射動(dòng)態(tài)加載類(lèi),并用該類(lèi)創(chuàng)建實(shí)例對(duì)象
動(dòng)態(tài)加載類(lèi)
我們用普通的方式使用一個(gè)類(lèi)的時(shí)候,類(lèi)是靜態(tài)加載的
而使用Class.forName("XXX")這種方式,則屬于動(dòng)態(tài)加載一個(gè)類(lèi)
靜態(tài)加載的類(lèi)在編譯的時(shí)候就能確定該類(lèi)是否存在,但動(dòng)態(tài)加載一個(gè)類(lèi)的時(shí)候卻無(wú)法在編譯階段確定是否存在該類(lèi),而是在運(yùn)行時(shí)候才能夠確定是否有這個(gè)類(lèi),所以要捕捉可能發(fā)生的異常
我們可以從eclipse的使用上有個(gè)相對(duì)直觀(guān)的了解:
eclipse在保存的時(shí)候是可以自動(dòng)編譯的,SO
例如我們?nèi)绻苯邮褂靡粋€(gè)本就不存在的類(lèi)NotExistClass的時(shí)候

保存后,在編譯階段就能夠發(fā)現(xiàn):“誒? 好像沒(méi)有這個(gè)類(lèi)哦!”
報(bào)的錯(cuò)誤是:
NotExistClass cannot be resolved to a type
但是如果我們用Class.forName("XXX")動(dòng)態(tài)加載一個(gè)類(lèi)呢?
我們發(fā)現(xiàn),保存后,在編譯階段已經(jīng)不能發(fā)現(xiàn)這個(gè)錯(cuò)誤了,對(duì)應(yīng)的是要捕捉可能發(fā)生的異常

用該動(dòng)態(tài)加載的類(lèi)創(chuàng)建實(shí)例對(duì)象
Class對(duì)象有一個(gè)newInstance方法,我們可以用它來(lái)創(chuàng)建實(shí)例對(duì)象
Class classInstance = Class.forName("mypackage.MyClass");
MyClass myClass = (MyClass) classInstance.newInstance();
不過(guò)要注意的是,因?yàn)閚ewInstance返回的是一個(gè)Object,所以要做強(qiáng)制類(lèi)型轉(zhuǎn)換,將其變成MyClass類(lèi)型
捕捉可能產(chǎn)生的異常后:
public class Test {
public static void main(String [] args) {
try {
Class classInstance = Class.forName("mypackage.MyClass");
MyClass myClass = (MyClass) classInstance.newInstance();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
總結(jié)
1.反射為我們提供了全面的分析類(lèi)信息的能力,例如類(lèi)的方法,成員變量和構(gòu)造器等的相關(guān)信息,反射能夠讓我們很方便的獲取這些信息, 而實(shí)現(xiàn)這個(gè)獲取過(guò)程的關(guān)鍵是取得類(lèi)的Class對(duì)象,然后根據(jù)Class對(duì)象取得相應(yīng)的Method對(duì)象,F(xiàn)ield對(duì)象和Constructor對(duì)象,再分別根據(jù)各自的API取得信息。
2.反射還為我們提供動(dòng)態(tài)加載類(lèi)的能力
一些細(xì)節(jié)
- API中g(shù)etDeclaredXXX和getXXX的區(qū)別在于前者只獲取本類(lèi)聲明的XXX(如成員變量或方法),而不獲取超類(lèi)中繼承的XXX, 后者相反
- API中, getXXXs(注意后面的s)返回的是一個(gè)數(shù)組, 而對(duì)應(yīng)的 getXXX("鍵")按鍵獲取一個(gè)值(這個(gè)時(shí)候因?yàn)榭赡軋?bào)已檢查異常所以要用try-catch語(yǔ)句包裹)
- 私有成員變量是不能直接獲取到值的!因?yàn)閖ava本身的保護(hù)機(jī)制,允許你取得私有成員變量的類(lèi)型,但是不允許直接獲取值,所以要對(duì)對(duì)應(yīng)的field對(duì)象調(diào)用field.setAccessible(true) 放開(kāi)權(quán)限