Java反射機(jī)制可以讓我們在編譯期(Compile Time)之外的運(yùn)行期(Runtime)獲得任何一個(gè)類的字節(jié)碼。包括接口、變量条舔、方法等信息。還可以讓我們在運(yùn)行期實(shí)例化對象怨咪,通過調(diào)用get/set方法獲取變量的值屋剑。
下面是一個(gè)小例子,讓大家感受下Java反射的魔力
Method[] methods = MyObject.class.getMethods();
for(Method method : methods){
System.out.println("method = " + method.getName());
}
這個(gè)例子通過調(diào)用類的class屬性獲取對應(yīng)的class對象诗眨,通過這個(gè) Class 類的對象獲取 MyObject 類中的方法集合
Class myObjectClass = MyObject.class;
使用 Java 反射機(jī)制可以在運(yùn)行時(shí)期檢查 Java 類的信息唉匾,檢查 Java 類的信息往往是你在使用 Java 反射機(jī)制的時(shí)候所做的第一件事情,通過獲取類的信息你可以獲取以下相關(guān)的內(nèi)容:
1.Class 對象
在你想檢查一個(gè)類的信息之前匠楚,你首先需要獲取類的 Class 對象巍膘。Java 中的所有類型包括基本類型(int, long, float等等),即使是數(shù)組都有與之關(guān)聯(lián)的 Class 類的對象芋簿。如果你在編譯期知道一個(gè)類的名字的話峡懈,那么你可以使用如下的方式獲取一個(gè)類的 Class 對象。
String className = ... ;//在運(yùn)行期獲取的類名字符串
Class class = Class.forName(className);
2.類名
如果你在編譯期不知道類的名字与斤,但是你可以在運(yùn)行期獲得到類名的字符串,那么你則可以這么做來獲取 Class 對象:
你可以從 Class 對象中獲取兩個(gè)版本的類名肪康。
通過 getName() 方法返回類的全限定類名(包含包名):
Class aClass = ... //獲取Class對象,具體方式可見Class對象小節(jié)
String className = aClass.getName();
如果你僅僅只是想獲取類的名字(不包含包名)撩穿,那么你可以使用 getSimpleName()方法:
Class aClass = ... //獲取Class對象磷支,具體方式可見Class對象小節(jié)
String simpleClassName = aClass.getSimpleName();
這個(gè)方法對讀別人(龐大復(fù)雜的app)的代碼特別有用,就拿安卓來說吧食寡,你可以在Activity 或者是自己定義的BaseActivity中打印該className or simpleClassName
Log.i(simpleClassName,"Someting");
//這樣你就知道自己每次打開的是具體哪個(gè)activity了雾狈,對閱讀捋清App功能邏輯很有幫助
3.修飾符
可以通過 Class 對象來訪問一個(gè)類的修飾符, 即public,private,static 等等的關(guān)鍵字抵皱,你可以使用如下方法來獲取類的修飾符:
Class aClass = ... //獲取Class對象善榛,具體方式可見Class對象小節(jié)
int modifiers = aClass.getModifiers();
修飾符都被包裝成一個(gè)int類型的數(shù)字,這樣每個(gè)修飾符都是一個(gè)位標(biāo)識(flag bit)叨叙,這個(gè)位標(biāo)識可以設(shè)置和清除修飾符的類型锭弊。 可以使用 java.lang.reflect.Modifier 類中的方法來檢查修飾符的類型:
Modifier.isAbstract(int modifiers);
Modifier.isFinal(int modifiers);
Modifier.isInterface(int modifiers);
Modifier.isNative(int modifiers);
Modifier.isPrivate(int modifiers);
Modifier.isProtected(int modifiers);
Modifier.isPublic(int modifiers);
Modifier.isStatic(int modifiers);
Modifier.isStrict(int modifiers);
Modifier.isSynchronized(int modifiers);
Modifier.isTransient(int modifiers);
Modifier.isVolatile(int modifiers);
4.包信息
可以使用 Class 對象通過如下的方式獲取包信息:
Class aClass = ... //獲取Class對象,具體方式可見Class對象小節(jié)
Package package = aClass.getPackage();
通過 Package 對象你可以獲取包的相關(guān)信息擂错,比如包名味滞,你也可以通過 Manifest 文件訪問位于編譯路徑下 jar 包的指定信息,比如你可以在 Manifest 文件中指定包的版本編號。更多的 Package 類信息可以閱讀 java.lang.Package剑鞍。
5.父類
通過 Class 對象你可以訪問類的父類昨凡,如下例:
Class superclass = aClass.getSuperclass();
可以看到 superclass 對象其實(shí)就是一個(gè) Class 類的實(shí)例,所以你可以繼續(xù)在這個(gè)對象上進(jìn)行反射操作蚁署。
6.實(shí)現(xiàn)的接口
可以通過如下方式獲取指定類所實(shí)現(xiàn)的接口集合:
Class aClass = ... //獲取Class對象便脊,具體方式可見Class對象小節(jié)
Class[] interfaces = aClass.getInterfaces();
由于一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,因此 getInterfaces(); 方法返回一個(gè) Class 數(shù)組光戈,在 Java 中接口同樣有對應(yīng)的 Class 對象哪痰。 注意:getInterfaces() 方法僅僅只返回當(dāng)前類所實(shí)現(xiàn)的接口。當(dāng)前類的父類如果實(shí)現(xiàn)了接口久妆,這些接口是不會(huì)在返回的 Class 集合中的晌杰,盡管實(shí)際上當(dāng)前類其實(shí)已經(jīng)實(shí)現(xiàn)了父類接口。
7.構(gòu)造器
我們可以通 過 Class 對象來獲取 Constructor 類的實(shí)例:
Class aClass = ...//獲取Class對象
Constructor[] constructors = aClass.getConstructors();
返回的 Constructor 數(shù)組包含每一個(gè)聲明為公有的(Public)構(gòu)造方法筷弦。 如果你知道你要訪問的構(gòu)造方法的方法參數(shù)類型肋演,你可以用下面的方法獲取指定的構(gòu)造方法,這例子返回的構(gòu)造方法的方法參數(shù)為 String 類型:
Class aClass = ...//獲取Class對象
Constructor constructor =
aClass.getConstructor(new Class[]{String.class});
如果沒有指定的構(gòu)造方法能滿足匹配的方法參數(shù)則會(huì)拋出:NoSuchMethodException烂琴。
構(gòu)造方法參數(shù)
你可以通過如下方式獲取指定構(gòu)造方法的方法參數(shù)信息:
Constructor constructor = ... //獲取Constructor對象
Class[] parameterTypes = constructor.getParameterTypes();
利用 Constructor 對象實(shí)例化一個(gè)類
你可以通過如下方法實(shí)例化一個(gè)類:
Constructor constructor = MyObject.class.getConstructor(String.class);
MyObject myObject = (MyObject)
constructor.newInstance("constructor-arg1");
constructor.newInstance()方法的方法參數(shù)是一個(gè)可變參數(shù)列表爹殊,但是當(dāng)你調(diào)用構(gòu)造方法的時(shí)候你必須提供精確的參數(shù),即形參與實(shí)參必須一一對應(yīng)奸绷。在這個(gè)例子中構(gòu)造方法需要一個(gè) String 類型的參數(shù)梗夸,那我們在調(diào)用 newInstance 方法的時(shí)候就必須傳入一個(gè) String 類型的參數(shù)。
8.方法
可以通過 Class 對象獲取 Method 對象健盒,如下例:
Class aClass = ...//獲取Class對象
Method[] methods = aClass.getMethods();
返回的 Method 對象數(shù)組包含了指定類中聲明為公有的(public)的所有變量集合绒瘦。
如果你知道你要調(diào)用方法的具體參數(shù)類型,你就可以直接通過參數(shù)類型來獲取指定的方法扣癣,下面這個(gè)例子中返回方法對象名稱是“doSomething”惰帽,他的方法參數(shù)是 String 類型:
Class aClass = ...//獲取Class對象
Method method = aClass.getMethod("doSomething", new Class[]{String.class});
如果根據(jù)給定的方法名稱以及參數(shù)類型無法匹配到相應(yīng)的方法,則會(huì)拋出 NoSuchMethodException父虑。 如果你想要獲取的方法沒有參數(shù)该酗,那么在調(diào)用 getMethod()方法時(shí)第二個(gè)參數(shù)傳入 null 即可,就像這樣:
Class aClass = ...//獲取Class對象
Method method = aClass.getMethod("doSomething", null);
方法參數(shù)以及返回類型
你可以獲取指定方法的方法參數(shù)是哪些:
Method method = ... //獲取Class對象
Class[] parameterTypes = method.getParameterTypes();
你可以獲取指定方法的返回類型:
Method method = ... //獲取Class對象
Class returnType = method.getReturnType();
通過 Method 對象調(diào)用方法
你可以通過如下方式來調(diào)用一個(gè)方法:
//獲取一個(gè)方法名為doSomesthing士嚎,參數(shù)類型為String的方法
Method method = MyObject.class.getMethod("doSomething", String.class);
Object returnValue = method.invoke(null, "parameter-value1");
傳入的 null 參數(shù)是你要調(diào)用方法的對象呜魄,如果是一個(gè)靜態(tài)方法調(diào)用的話則可以用 null 代替指定對象作為 invoke()的參數(shù),在上面這個(gè)例子中莱衩,如果 doSomething 不是靜態(tài)方法的話爵嗅,你就要傳入有效的 MyObject 實(shí)例而不是 null。 Method.invoke(Object target, Object … parameters)方法的第二個(gè)參數(shù)是一個(gè)可變參數(shù)列表笨蚁,但是你必須要傳入與你要調(diào)用方法的形參一一對應(yīng)的實(shí)參睹晒。就像上個(gè)例子那樣趟庄,方法需要 String 類型的參數(shù),那我們必須要傳入一個(gè)字符串伪很。
9.變量
你可以通過如下方式訪問一個(gè)類的成員變量:
Field[] method = aClass.getFields();
在通常的觀點(diǎn)中從對象的外部訪問私有變量以及方法是不允許的戚啥,但是 Java 反射機(jī)制可以做到這一點(diǎn)。使用這個(gè)功能并不困難锉试,在進(jìn)行單元測試時(shí)這個(gè)功能非常有效猫十。本節(jié)會(huì)向你展示如何使用這個(gè)功能。
注意:這個(gè)功能只有在代碼運(yùn)行在單機(jī) Java 應(yīng)用(standalone Java application)中才會(huì)有效,就像你做單元測試或者一些常規(guī)的應(yīng)用程序一樣呆盖。如果你在 Java Applet 中使用這個(gè)功能拖云,那么你就要想辦法去應(yīng)付 SecurityManager 對你限制了。但是一般情況下我們是不會(huì)這么做的应又,所以在本節(jié)里面我們不會(huì)探討這個(gè)問題江兢。
訪問私有變量
要想獲取私有變量你可以調(diào)用 Class.getDeclaredField(String name)方法或者 Class.getDeclaredFields()方法。
Class.getField(String name)和 Class.getFields()只會(huì)返回公有的變量丁频,無法獲取私有變量。下面例子定義了一個(gè)包含私有變量的類邑贴,在它下面是如何通過反射獲取私有變量的例子:
public class PrivateObject {
private String privateString = null;
public PrivateObject(String privateString) {
this.privateString = privateString;
}
}
PrivateObject privateObject = new PrivateObject("The Private Value");
Field privateStringField = PrivateObject.class.
getDeclaredField("privateString");
privateStringField.setAccessible(true);
String fieldValue = (String) privateStringField.get(privateObject);
System.out.println("fieldValue = " + fieldValue);
這個(gè)例子會(huì)輸出”fieldValue = The Private Value”席里,The Private Value 是 PrivateObject 實(shí)例的 privateString 私有變量的值,注意調(diào)用 PrivateObject.class.getDeclaredField(“privateString”)方法會(huì)返回一個(gè)私有變量拢驾,這個(gè)方法返回的變量是定義在 PrivateObject 類中的而不是在它的父類中定義的變量奖磁。 注意 privateStringField.setAccessible(true)這行代碼,通過調(diào)用 setAccessible()方法會(huì)關(guān)閉指定類 Field 實(shí)例的反射訪問檢查繁疤,這行代碼執(zhí)行之后不論是私有的咖为、受保護(hù)的以及包訪問的作用域,你都可以在任何地方訪問稠腊,即使你不在他的訪問權(quán)限作用域之內(nèi)躁染。但是你如果你用一般代碼來訪問這些不在你權(quán)限作用域之內(nèi)的代碼依然是不可以的,在編譯的時(shí)候就會(huì)報(bào)錯(cuò)架忌。
訪問私有方法
訪問一個(gè)私有方法你需要調(diào)用 Class.getDeclaredMethod(String name, Class[] parameterTypes)或者 Class.getDeclaredMethods() 方法吞彤。 Class.getMethod(String name, Class[] parameterTypes)和 Class.getMethods()方法,只會(huì)返回公有的方法叹放,無法獲取私有方法饰恕。下面例子定義了一個(gè)包含私有方法的類,在它下面是如何通過反射獲取私有方法的例子:
public class PrivateObject {
private String privateString = null;
public PrivateObject(String privateString) {
this.privateString = privateString;
}
private String getPrivateString(){
return this.privateString;
}
}
PrivateObject privateObject = new PrivateObject("The Private Value");
Method privateStringMethod = PrivateObject.class.
getDeclaredMethod("getPrivateString", null);
privateStringMethod.setAccessible(true);
String returnValue = (String)
privateStringMethod.invoke(privateObject, null);
System.out.println("returnValue = " + returnValue);
這個(gè)例子會(huì)輸出”returnValue = The Private Value”井仰,The Private Value 是 PrivateObject 實(shí)例的 getPrivateString()方法的返回值埋嵌。 PrivateObject.class.getDeclaredMethod(“privateString”)方法會(huì)返回一個(gè)私有方法,這個(gè)方法是定義在 PrivateObject 類中的而不是在它的父類中定義的俱恶。 同樣的雹嗦,注意 Method.setAcessible(true)這行代碼范舀,通過調(diào)用 setAccessible()方法會(huì)關(guān)閉指定類的 Method 實(shí)例的反射訪問檢查,這行代碼執(zhí)行之后不論是私有的俐银、受保護(hù)的以及包訪問的作用域尿背,你都可以在任何地方訪問,即使你不在他的訪問權(quán)限作用域之內(nèi)捶惜。但是你如果你用一般代碼來訪問這些不在你權(quán)限作用域之內(nèi)的代碼依然是不可以的田藐,在編譯的時(shí)候就會(huì)報(bào)錯(cuò)。
10.泛型
運(yùn)用泛型反射的經(jīng)驗(yàn)法則
下面是兩個(gè)典型的使用泛型的場景:
1吱七、聲明一個(gè)需要被參數(shù)化(parameterizable)的類/接口汽久。
2、使用一個(gè)參數(shù)化類踊餐。
當(dāng)你聲明一個(gè)類或者接口的時(shí)候你可以指明這個(gè)類或接口可以被參數(shù)化景醇, java.util.List 接口就是典型的例子。你可以運(yùn)用泛型機(jī)制創(chuàng)建一個(gè)標(biāo)明存儲(chǔ)的是 String 類型 list吝岭,這樣比你創(chuàng)建一個(gè) Object 的l ist 要更好三痰。
當(dāng)你想在運(yùn)行期參數(shù)化類型本身,比如你想檢查 java.util.List 類的參數(shù)化類型窜管,你是沒有辦法能知道他具體的參數(shù)化類型是什么散劫。這樣一來這個(gè)類型就可以是一個(gè)應(yīng)用中所有的類型。但是幕帆,當(dāng)你檢查一個(gè)使用了被參數(shù)化的類型的變量或者方法获搏,你可以獲得這個(gè)被參數(shù)化類型的具體參數(shù)。
總之:
你不能在運(yùn)行期獲知一個(gè)被參數(shù)化的類型的具體參數(shù)類型是什么失乾,但是你可以在用到這個(gè)被參數(shù)化類型的方法以及變量中找到他們常熙,換句話說就是獲知他們具體的參數(shù)化類型。 在下面的段落中會(huì)向你演示這類情況碱茁。
泛型方法返回類型
如果你獲得了 java.lang.reflect.Method 對象裸卫,那么你就可以獲取到這個(gè)方法的泛型返回類型信息。如果方法是在一個(gè)被參數(shù)化類型之中(譯者注:如 T fun())那么你無法獲取他的具體類型纽竣,但是如果方法返回一個(gè)泛型類(譯者注:如 List fun())那么你就可以獲得這個(gè)泛型類的具體參數(shù)化類型彼城。你可以在“Java Reflection: Methods”中閱讀到有關(guān)如何獲取Method對象的相關(guān)內(nèi)容。下面這個(gè)例子定義了一個(gè)類這個(gè)類中的方法返回類型是一個(gè)泛型類型:
public class MyClass {
protected List<String> stringList = ...;
public List<String> getStringList(){
return this.stringList;
}
}
我們可以獲取 getStringList()方法的泛型返回類型退个,換句話說募壕,我們可以檢測到 getStringList()方法返回的是 List 而不僅僅只是一個(gè) List。如下例:
Method method = MyClass.class.getMethod("getStringList", null);
Type returnType = method.getGenericReturnType();
if(returnType instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType) returnType;
Type[] typeArguments = type.getActualTypeArguments();
for(Type typeArgument : typeArguments){
Class typeArgClass = (Class) typeArgument;
System.out.println("typeArgClass = " + typeArgClass);
}
}
這段代碼會(huì)打印出 “typeArgClass = java.lang.String”语盈,Type[]數(shù)組typeArguments 只有一個(gè)結(jié)果 – 一個(gè)代表 java.lang.String 的 Class 類的實(shí)例舱馅。Class 類實(shí)現(xiàn)了 Type 接口。
泛型方法參數(shù)類型
你同樣可以通過反射來獲取方法參數(shù)的泛型類型刀荒,下面這個(gè)例子定義了一個(gè)類代嗤,這個(gè)類中的方法的參數(shù)是一個(gè)被參數(shù)化的 List:
public class MyClass {
protected List<String> stringList = ...;
public void setStringList(List<String> list){
this.stringList = list;
}
}
你可以像這樣來獲取方法的泛型參數(shù):
method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
這段代碼會(huì)打印出”parameterArgType = java.lang.String”棘钞。Type[]數(shù)組 parameterArgTypes 只有一個(gè)結(jié)果 – 一個(gè)代表 java.lang.String 的 Class 類的實(shí)例。Class 類實(shí)現(xiàn)了Type接口干毅。
泛型變量類型
同樣可以通過反射來訪問公有(Public)變量的泛型類型宜猜,無論這個(gè)變量是一個(gè)類的靜態(tài)成員變量或是實(shí)例成員變量。你可以在“Java Reflection: Fields”中閱讀到有關(guān)如何獲取 Field 對象的相關(guān)內(nèi)容硝逢。這是之前的一個(gè)例子姨拥,一個(gè)定義了一個(gè)名為 stringList 的成員變量的類。
method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
這段代碼會(huì)打印出”fieldArgClass = java.lang.String”渠鸽。Type[]數(shù)組 fieldArgClass 只有一個(gè)結(jié)果 – 一個(gè)代表 java.lang.String 的 Class 類的實(shí)例叫乌。Class 類實(shí)現(xiàn)了 Type 接口。