對(duì)于Java反射遍膜,平常工作中雖然經(jīng)常用到,但一直以來(lái)都沒(méi)有系統(tǒng)總結(jié)過(guò)瓤湘,所以趁著目前有空總結(jié)一下瓢颅,加深一下理解。
如果發(fā)現(xiàn)謬誤弛说,歡迎各位批評(píng)指正挽懦。
本文相關(guān)知識(shí)點(diǎn)大部分總結(jié)自Oracle官方文檔,對(duì)于英文比較好的朋友木人,建議直接閱讀原文檔信柿。
按例,首先描述定義一下醒第。
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
通過(guò)反射角塑,Java代碼可以發(fā)現(xiàn)有關(guān)已加載類的字段,方法和構(gòu)造函數(shù)的信息淘讥,并可以在安全限制內(nèi)對(duì)這些字段,方法和構(gòu)造函數(shù)進(jìn)行操作堤如。
簡(jiǎn)而言之蒲列,你可以在運(yùn)行狀態(tài)中通過(guò)反射機(jī)制做到:
- 對(duì)于任意一個(gè)類窒朋,都能夠知道這個(gè)類的所有屬性和方法;
- 對(duì)于任意一個(gè)對(duì)象蝗岖,都能夠調(diào)用它的任意一個(gè)方法和屬性;
這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為java語(yǔ)言的反射機(jī)制侥猩。
在我看來(lái)我們平時(shí)使用Java反射主要涉及兩個(gè)類(接口)Class
, Member
抵赢,如果把這兩個(gè)類搞清楚了欺劳,反射基本就ok了。
Class
提到反射就不得不提到Class铅鲤,Class可以說(shuō)是反射能夠?qū)崿F(xiàn)的基礎(chǔ)划提;注意這里說(shuō)的Class與class關(guān)鍵字不是同一種東西。class關(guān)鍵字是在聲明java類時(shí)使用的邢享;而Class 是java JDK提供的一個(gè)類,完整路徑為 java.lang.Class
鹏往,本質(zhì)上與Math, String 或者你自己定義各種類沒(méi)什么區(qū)別。
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement {
...
}
那Class到底在反射中起到什么作用呢骇塘?
For every type of object, the Java virtual machine instantiates an immutable instance of
java.lang.Class
which provides methods to examine the runtime properties of the object including its members and type information.Class
also provides the ability to create new classes and objects. Most importantly, it is the entry point for all of the Reflection APIs.
對(duì)于每一種類伊履,Java虛擬機(jī)都會(huì)初始化出一個(gè)Class類型的實(shí)例,每當(dāng)我們編寫(xiě)并且編譯一個(gè)新創(chuàng)建的類就會(huì)產(chǎn)生一個(gè)對(duì)應(yīng)Class對(duì)象款违,并且這個(gè)Class對(duì)象會(huì)被保存在同名.class文件里唐瀑。當(dāng)我們new一個(gè)新對(duì)象或者引用靜態(tài)成員變量時(shí),Java虛擬機(jī)(JVM)中的類加載器系統(tǒng)會(huì)將對(duì)應(yīng)Class對(duì)象加載到JVM中插爹,然后JVM再根據(jù)這個(gè)類型信息相關(guān)的Class對(duì)象創(chuàng)建我們需要實(shí)例對(duì)象或者提供靜態(tài)變量的引用值哄辣。
比如創(chuàng)建編譯一個(gè)Shapes類,那么递惋,JVM就會(huì)創(chuàng)建一個(gè)Shapes對(duì)應(yīng)Class類的Class實(shí)例柔滔,該Class實(shí)例保存了Shapes類相關(guān)的類型信息,包括屬性萍虽,方法睛廊,構(gòu)造方法等等,通過(guò)這個(gè)Class實(shí)例可以在運(yùn)行時(shí)訪問(wèn)Shapes對(duì)象的屬性和方法等杉编。另外通過(guò)Class類還可以創(chuàng)建出一個(gè)新的Shapes對(duì)象超全。這就是反射能夠?qū)崿F(xiàn)的原因,可以說(shuō)Class是反射操作的基礎(chǔ)邓馒。
需要特別注意的是嘶朱,每個(gè)class(注意class是小寫(xiě),代表普通類)類光酣,無(wú)論創(chuàng)建多少個(gè)實(shí)例對(duì)象疏遏,在JVM中都對(duì)應(yīng)同一個(gè)Class對(duì)象。
下面就通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明如何通過(guò)反射實(shí)例化一個(gè)對(duì)象。
public class Animal {
private String name;
private int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Animal : name = " + name + " age = " + age;
}
}
public class TestReflection{
private static final String TAG = "Reflection";
public void testReflection(){
//獲取Animal類的Class對(duì)象
Class c = Animal.class;
try {
//通過(guò)Class對(duì)象反射獲取Animal類的構(gòu)造方法
Constructor constructor = c.getConstructor(String.class, int.class);
//調(diào)用構(gòu)造方法獲取Animal實(shí)例
Animal animal = (Animal) constructor.newInstance( "Jack", 3);
//將構(gòu)造出來(lái)的Animal對(duì)象打印出來(lái)
Log.d(TAG, animal.toString());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
下面我們來(lái)看下打印值
03-28 20:12:00.958 2835-2835/? D/Reflection: Animal : name = Jack age = 3
可以看出我們確實(shí)成功構(gòu)造出了Animal對(duì)象财异,而且在這過(guò)程中Class功不可沒(méi)倘零。
有人說(shuō)你這也太費(fèi)事了,都知道Animal對(duì)象了戳寸,我分分鐘就能給你new出來(lái)了呈驶。
Animal animal = new Animal("Jack", 3);
沒(méi)錯(cuò)!
但是如果并不能直接導(dǎo)入Animal類呢疫鹊,如果構(gòu)造方法都是private的呢袖瞻?這個(gè)時(shí)候反射就能大展身手了。
如何獲取Class
說(shuō)Class是反射能夠?qū)崿F(xiàn)的基礎(chǔ)的另一個(gè)原因是:Java反射包java.lang.reflect
中的所有類都沒(méi)有public構(gòu)造方法拆吆,要想獲得這些類實(shí)例聋迎,只能通過(guò)Class類獲取。所以說(shuō)如果想使用反射锈拨,必須得獲得Class對(duì)象砌庄。
下面列舉了幾種能夠獲取Class對(duì)象的方法。
-
Object.getClass()
通過(guò)對(duì)象實(shí)例獲取對(duì)應(yīng)Class對(duì)象奕枢,如
//Returns the Class for String
Class c = "foo".getClass();
enum E { A, B }
//Returns the Class corresponding to the enumeration type E.
Class c = A.getClass();
byte[] bytes = new byte[1024];
//Returns the Class corresponding to an array with component type byte.
Class c = bytes.getClass();
Set<String> s = new HashSet<String>();
//Returns the Class corresponding to java.util.HashSet.
Class c = s.getClass();
然而對(duì)于基本類型無(wú)法使用這種方法
boolean b;
Class c = b.getClass(); // compile-time error
-
The .class Syntax
通過(guò)類的類型獲取Class對(duì)象,基本類型同樣可以使用這種方法娄昆,如
//The `.class` syntax returns the Class corresponding to the type `boolean`.
Class c = boolean.class;
//Returns the Class for String
Class c = String.class;
-
Class.forName()
通過(guò)類的全限定名獲取Class對(duì)象, 基本類型無(wú)法使用此方法
Class c = Class.forName("java.lang.String");
對(duì)于數(shù)組比較特殊
Class cDoubleArray = Class.forName("[D"); //相當(dāng)于double[].class
Class cStringArray = Class.forName("[[Ljava.lang.String;"); //相當(dāng)于String[][].class
-
TYPE Field for Primitive Type Wrappers
基本類型和void 類型的包裝類可以使用TYPE字段獲取
Class c = Double.TYPE; //等價(jià)于 double.class.
Class c = Void.TYPE;
-
Methods that Return Classes
另外還有一些反射方法可以獲取Class對(duì)象缝彬,但前提是你已經(jīng)獲取了一個(gè)Class對(duì)象萌焰。
有點(diǎn)拗口,比如說(shuō)你已經(jīng)獲取了一個(gè)類的Class對(duì)象谷浅,就可以通過(guò)反射方法獲取這個(gè)類的父類的Class對(duì)象扒俯。
Class.getSuperclass()
獲得給定類的父類Class
// javax.swing.JButton的父類是javax.swing.AbstractButton
Class c = javax.swing.JButton.class.getSuperclass();
類似方法還有:
Class.getClasses()
Class.getDeclaredClasses()
Class.getDeclaringClass()
Class.getEnclosingClass()
java.lang.reflect.Field.getDeclaringClass()
java.lang.reflect.Method.getDeclaringClass()
java.lang.reflect.Constructor.getDeclaringClass()
通過(guò)Class獲取類修飾符和類型
我們知道類的聲明一般如下表示
[圖片上傳失敗...(image-7d6d12-1528676523570)]
下面我們就以HashMap為例,通過(guò)一個(gè)Demo來(lái)說(shuō)明如何獲取這些信息
public class TestReflection {
private static final String TAG = "Reflection";
public void testReflection() {
Class<?> c = HashMap.class;
//獲取類名
Log.d(TAG, "Class : " + c.getCanonicalName());
//獲取類限定符
Log.d(TAG, "Modifiers : " + Modifier.toString(c.getModifiers()));
//獲取類泛型信息
TypeVariable[] tv = c.getTypeParameters();
if (tv.length != 0) {
StringBuilder parameter = new StringBuilder("Parameters : ");
for (TypeVariable t : tv) {
parameter.append(t.getName());
parameter.append(" ");
}
Log.d(TAG, parameter.toString());
} else {
Log.d(TAG, " -- No Type Parameters --");
}
//獲取類實(shí)現(xiàn)的所有接口
Type[] intfs = c.getGenericInterfaces();
if (intfs.length != 0) {
StringBuilder interfaces = new StringBuilder("Implemented Interfaces : ");
for (Type intf : intfs){
interfaces.append(intf.toString());
interfaces.append(" ");
}
Log.d(TAG, interfaces.toString());
} else {
Log.d(TAG, " -- No Implemented Interfaces --");
}
//獲取類繼承數(shù)上的所有父類
List<Class> l = new ArrayList<>();
printAncestor(c, l);
if (l.size() != 0) {
StringBuilder inheritance = new StringBuilder("Inheritance Path : ");
for (Class<?> cl : l){
inheritance.append(cl.getCanonicalName());
inheritance.append(" ");
}
Log.d(TAG, inheritance.toString());
} else {
Log.d(TAG, " -- No Super Classes --%n%n");
}
//獲取類的注解(只能獲取到 RUNTIME 類型的注解)
Annotation[] ann = c.getAnnotations();
if (ann.length != 0) {
StringBuilder annotation = new StringBuilder("Annotations : ");
for (Annotation a : ann){
annotation.append(a.toString());
annotation.append(" ");
}
Log.d(TAG, annotation.toString());
} else {
Log.d(TAG, " -- No Annotations --%n%n");
}
}
private static void printAncestor(Class<?> c, List<Class> l) {
Class<?> ancestor = c.getSuperclass();
if (ancestor != null) {
l.add(ancestor);
printAncestor(ancestor, l);
}
}
}
打印結(jié)果如下
03-29 15:04:23.070 27826-27826/com.example.ming.testproject D/Reflection: Class : java.util.HashMap
03-29 15:04:23.070 27826-27826/com.example.ming.testproject D/Reflection: Modifiers : public
03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: Parameters : K V
03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: Implemented Interfaces : java.util.Map<K, V> interface java.lang.Cloneable interface java.io.Serializable
03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: Inheritance Path : java.util.AbstractMap java.lang.Object
03-29 15:04:23.071 27826-27826/com.example.ming.testproject D/Reflection: -- No Annotations --
Member
Reflection defines an interface
java.lang.reflect.Member
which is implemented byjava.lang.reflect.Field
,java.lang.reflect.Method
, andjava.lang.reflect.Constructor
.
對(duì)于Member接口可能會(huì)有人不清楚是干什么的一疯,但如果提到實(shí)現(xiàn)它的三個(gè)實(shí)現(xiàn)類撼玄,估計(jì)用過(guò)反射的人都能知道。我們知道類成員主要包括構(gòu)造函數(shù)
墩邀,變量
和方法
掌猛,Java中的操作基本都和這三者相關(guān),而Member的這三個(gè)實(shí)現(xiàn)類就分別對(duì)應(yīng)他們眉睹。
java.lang.reflect.Field
:對(duì)應(yīng)類變量
java.lang.reflect.Method
:對(duì)應(yīng)類方法
java.lang.reflect.Constructor
:對(duì)應(yīng)類構(gòu)造函數(shù)
反射就是通過(guò)這三個(gè)類才能在運(yùn)行時(shí)改變對(duì)象狀態(tài)荔茬。下面就讓我們通過(guò)一些例子來(lái)說(shuō)明如何通過(guò)反射操作它們。
首先建一個(gè)測(cè)試類
public class Cat {
public static final String TAG = Cat.class.getSimpleName();
private String name;
@Deprecated
public int age;
public Cat(String name, int age){
this.name = name;
this.age = age;
}
public String getName(){
return name;
}
public void eat(String food){
Log.d(TAG, "eat food " + food);
}
public void eat(String... foods){
StringBuilder s = new StringBuilder();
for(String food : foods){
s.append(food);
s.append(" ");
}
Log.d(TAG, "eat food " + s.toString());
}
public void sleep(){
Log.d(TAG, "sleep");
}
@Override
public String toString() {
return "name = " + name + " age = " + age;
}
}
Field
通過(guò)Field你可以訪問(wèn)給定對(duì)象的類變量竹海,包括獲取變量的類型慕蔚、修飾符、注解斋配、變量名孔飒、變量的值或者重新設(shè)置變量值灌闺,即使變量是private的。
獲取Field
Class提供了4種方法獲得給定類的Field十偶。
getDeclaredField(String name)
獲取指定的變量(只要是聲明的變量都能獲得菩鲜,包括private)
getField(String name)
獲取指定的變量(只能獲得public的)
getDeclaredFields()
獲取所有聲明的變量(包括private)
getFields()
獲取所有的public變量獲取變量類型、修飾符惦积、注解
一個(gè)例子說(shuō)明問(wèn)題
public void testField(){
Class c = Cat.class;
Field[] fields = c.getDeclaredFields();
for(Field f : fields){
StringBuilder builder = new StringBuilder();
//獲取名稱
builder.append("filed name = ");
builder.append(f.getName());
//獲取類型
builder.append(" type = ");
builder.append(f.getType());
//獲取修飾符
builder.append(" modifiers = ");
builder.append(Modifier.toString(f.getModifiers()));
//獲取注解
Annotation[] ann = f.getAnnotations();
if (ann.length != 0) {
builder.append(" annotations = ");
for (Annotation a : ann){
builder.append(a.toString());
builder.append(" ");
}
} else {
builder.append(" -- No Annotations --");
}
Log.d(TAG, builder.toString());
}
}
打印結(jié)果:
filed name = age type = int modifiers = public annotations = @java.lang.Deprecated()
filed name = name type = class java.lang.String modifiers = private -- No Annotations --
filed name = TAG type = class java.lang.String modifiers = public static final -- No Annotations --
-
獲取、設(shè)置變量值
給定一個(gè)對(duì)象和它的成員變量名稱猛频,就能通過(guò)反射獲取和改變?cè)撟兞康闹怠?br> 什么都不說(shuō)了狮崩,沒(méi)有什么是不能通過(guò)一個(gè)例子解決的, Easy~
仍然是上面的測(cè)試類鹿寻,通過(guò)反射獲取并改變Cat的name和age.
public void testField(){
Cat cat = new Cat("Tom", 2);
Class c = cat.getClass();
try {
//注意獲取private變量時(shí)睦柴,需要用getDeclaredField
Field fieldName = c.getDeclaredField("name");
Field fieldAge = c.getField("age");
//反射獲取名字, 年齡
String name = (String) fieldName.get(cat);
int age = fieldAge.getInt(cat);
Log.d(TAG, "before set, Cat name = " + name + " age = " + age);
//反射重新set名字和年齡
fieldName.set(cat, "Timmy");
fieldAge.setInt(cat, 3);
Log.d(TAG, "after set, Cat " + cat.toString());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
嗯?竟然報(bào)錯(cuò)毡熏?
System.err: java.lang.IllegalAccessException: Class java.lang.Class<com.example.ming.testnestscrollview.TestReflection> cannot access private field java.lang.String com.example.ming.testnestscrollview.Cat.name of class java.lang.Class<com.example.ming.testnestscrollview.Cat>
System.err: at java.lang.reflect.Field.get(Native Method)
System.err: at com.example.ming.testnestscrollview.TestReflection.testField(TestReflection.java:22)
System.err: at com.example.ming.testnestscrollview.MainActivity.onCreate(MainActivity.java:17)
觀察一下異常信息java.lang.IllegalAccessException
坦敌,說(shuō)我們沒(méi)有權(quán)限操作變量name;回到Cat類中查看一下name變量痢法。
private String name;
原來(lái)name變量是private狱窘,Java運(yùn)行時(shí)會(huì)進(jìn)行訪問(wèn)權(quán)限檢查,private類型的變量無(wú)法進(jìn)行直接訪問(wèn)财搁,剛剛進(jìn)行的反射操作并沒(méi)有打破這種封裝蘸炸,所以我們依然沒(méi)有權(quán)限對(duì)private屬性進(jìn)行直接訪問(wèn)。
難道就沒(méi)有辦法打破這種限制嗎尖奔?必須有搭儒!強(qiáng)大的反射早已暗中為我們準(zhǔn)備好了一切。
反射包里為我們提供了一個(gè)強(qiáng)大的類
-
java.lang.reflect.AccessibleObject
AccessibleObject
為我們提供了一個(gè)方法 setAccessible(boolean flag)提茁,該方法的作用就是可以取消 Java 語(yǔ)言訪問(wèn)權(quán)限檢查淹禾。所以任何繼承AccessibleObject
的類的對(duì)象都可以使用該方法取消 Java 語(yǔ)言訪問(wèn)權(quán)限檢查。(final類型變量也可以通過(guò)這種辦法訪問(wèn))
public final class Field extends AccessibleObject implements Member
Field
正是AccessibleObject
的子類茴扁,那么簡(jiǎn)單了铃岔,只要在訪問(wèn)私有變量前調(diào)用filed.setAccessible(true)
就可以了
...
fieldName.setAccessible(true);
//反射獲取名字, 年齡
String name = (String) fieldName.get(cat);
...
打印結(jié)果
TestReflection: before set, Cat name = Tom age = 2
TestReflection: after set, Cat name = Timmy age = 3
Bingo!
注意Method
和Constructor
也都是繼承AccessibleObject
,所以如果遇到私有方法和私有構(gòu)造函數(shù)無(wú)法訪問(wèn)丹弱,記得處理方法一樣德撬。
Method
The
java.lang.reflect.Method
class provides APIs to access information about a method's modifiers, return type, parameters, annotations, and thrown exceptions. It also be used to invoke methods.
這節(jié)主要介紹如何通過(guò)反射訪問(wèn)對(duì)象的方法。
-
獲取Method
Class依然提供了4種方法獲取Method:
getDeclaredMethod(String name, Class<?>... parameterTypes)
根據(jù)方法名獲得指定的方法躲胳, 參數(shù)name為方法名蜓洪,參數(shù)parameterTypes為方法的參數(shù)類型,如 getDeclaredMethod(“eat”, String.class)
getMethod(String name, Class<?>... parameterTypes)
根據(jù)方法名獲取指定的public方法坯苹,其它同上
getDeclaredMethods()
獲取所有聲明的方法
getMethods()
獲取所有的public方法
注意:獲取帶參數(shù)方法時(shí)隆檀,如果參數(shù)類型錯(cuò)誤會(huì)報(bào)
NoSuchMethodException
,對(duì)于參數(shù)是泛型的情況,泛型須當(dāng)成Object處理(Object.class)
-
獲取方法返回類型
getReturnType()
獲取目標(biāo)方法返回類型對(duì)應(yīng)的Class對(duì)象
getGenericReturnType()
獲取目標(biāo)方法返回類型對(duì)應(yīng)的Type對(duì)象
這兩個(gè)方法有啥區(qū)別呢恐仑?- getReturnType()返回類型為Class泉坐,getGenericReturnType()返回類型為T(mén)ype; Class實(shí)現(xiàn)Type。
- 返回值為普通簡(jiǎn)單類型如Object, int, String等裳仆,getGenericReturnType()返回值和getReturnType()一樣
例如public String function1()
那么各自返回值為:
getReturnType() : class java.lang.String
getGenericReturnType() : class java.lang.String
- 返回值為泛型
例如public T function2()
那么各自返回值為:
getReturnType() : class java.lang.Object
getGenericReturnType() : T
- 返回值為參數(shù)化類型
例如public Class<String> function3()
那么各自返回值為:
getReturnType() : class java.lang.Class
getGenericReturnType() : java.lang.Class<java.lang.String>
其實(shí)反射中所有形如getGenericXXX()
的方法規(guī)則都與上面所述類似腕让。
獲取方法參數(shù)類型
getParameterTypes()
獲取目標(biāo)方法各參數(shù)類型對(duì)應(yīng)的Class對(duì)象
getGenericParameterTypes()
獲取目標(biāo)方法各參數(shù)類型對(duì)應(yīng)的Type對(duì)象
返回值為數(shù)組,它倆區(qū)別同上 “方法返回類型的區(qū)別” 歧斟。獲取方法聲明拋出的異常的類型
getExceptionTypes()
獲取目標(biāo)方法拋出的異常類型對(duì)應(yīng)的Class對(duì)象
getGenericExceptionTypes()
獲取目標(biāo)方法拋出的異常類型對(duì)應(yīng)的Type對(duì)象
返回值為數(shù)組纯丸,區(qū)別同上獲取方法參數(shù)名稱
.class文件中默認(rèn)不存儲(chǔ)方法參數(shù)名稱,如果想要獲取方法參數(shù)名稱静袖,需要在編譯的時(shí)候加上-parameters
參數(shù)觉鼻。(構(gòu)造方法的參數(shù)獲取方法同樣)
//這里的m可以是普通方法Method,也可以是構(gòu)造方法Constructor
//獲取方法所有參數(shù)
Parameter[] params = m.getParameters();
for (int i = 0; i < params.length; i++) {
Parameter p = params[i];
p.getType(); //獲取參數(shù)類型
p.getName(); //獲取參數(shù)名稱队橙,如果編譯時(shí)未加上`-parameters`坠陈,返回的名稱形如`argX`, X為參數(shù)在方法聲明中的位置,從0開(kāi)始
p.getModifiers(); //獲取參數(shù)修飾符
p.isNamePresent(); //.class文件中是否保存參數(shù)名稱, 編譯時(shí)加上`-parameters`返回true,反之flase
}
獲取方法參數(shù)名稱的詳細(xì)信息請(qǐng)參考o(jì)racle的官方例子MethodParameterSpy
-
獲取方法修飾符
方法與Filed等類似
method.getModifiers();
Ps:順便多介紹幾個(gè)Method方法
-
method.isVarArgs()
//判斷方法參數(shù)是否是可變參數(shù)
public Constructor<T> getConstructor(Class<?>... parameterTypes) //返回true
public Constructor<T> getConstructor(Class<?> [] parameterTypes) //返回flase
-
method.isSynthetic()
//判斷是否是復(fù)合方法捐康,個(gè)人理解復(fù)合方法是編譯期間編譯器生成的方法仇矾,并不是源代碼中有的方法 -
method.isBridge()
//判斷是否是橋接方法,橋接方法是 JDK 1.5 引入泛型后吹由,為了使Java的泛型方法生成的字節(jié)碼和 1.5 版本前的字節(jié)碼相兼容若未,由編譯器自動(dòng)生成的方法∏泠辏可以參考https://www.cnblogs.com/zsg88/p/7588929.html
-
通過(guò)反射調(diào)用方法
反射通過(guò)Method的invoke()
方法來(lái)調(diào)用目標(biāo)方法粗合。第一個(gè)參數(shù)為需要調(diào)用的目標(biāo)類對(duì)象,如果方法為static的乌昔,則該參數(shù)為null隙疚。后面的參數(shù)都為目標(biāo)方法的參數(shù)值,順序與目標(biāo)方法聲明中的參數(shù)順序一致磕道。
public native Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
還是以上面測(cè)試類Cat為例
注意:如果方法是private的供屉,可以使用
method.setAccessible(true)
方法繞過(guò)權(quán)限檢查
Class<?> c = Cat.class;
try {
//構(gòu)造Cat實(shí)例
Constructor constructor = c.getConstructor(String.class, int.class);
Object cat = constructor.newInstance( "Jack", 3);
//調(diào)用無(wú)參方法
Method sleep = c.getDeclaredMethod("sleep");
sleep.invoke(cat);
//調(diào)用定項(xiàng)參數(shù)方法
Method eat = c.getDeclaredMethod("eat", String.class);
eat.invoke(cat, "grass");
//調(diào)用不定項(xiàng)參數(shù)方法
//不定項(xiàng)參數(shù)可以當(dāng)成數(shù)組來(lái)處理
Class[] argTypes = new Class[] { String[].class };
Method varargsEat = c.getDeclaredMethod("eat", argTypes);
String[] foods = new String[]{
"grass", "meat"
};
varargsEat.invoke(cat, (Object)foods);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
被調(diào)用的方法本身所拋出的異常在反射中都會(huì)以
InvocationTargetException
拋出。換句話說(shuō)溺蕉,反射調(diào)用過(guò)程中如果異常InvocationTargetException
拋出伶丐,說(shuō)明反射調(diào)用本身是成功的,因?yàn)檫@個(gè)異常是目標(biāo)方法本身所拋出的異常疯特。
Constructor
這節(jié)主要介紹如何通過(guò)反射訪問(wèn)構(gòu)造方法并通過(guò)構(gòu)造方法構(gòu)建新的對(duì)象哗魂。
-
獲取構(gòu)造方法
和Method一樣,Class也為Constructor提供了4種方法獲取
getDeclaredConstructor(Class<?>... parameterTypes)
獲取指定構(gòu)造函數(shù)漓雅,參數(shù)parameterTypes為構(gòu)造方法的參數(shù)類型
getConstructor(Class<?>... parameterTypes)
獲取指定public構(gòu)造函數(shù)录别,參數(shù)parameterTypes為構(gòu)造方法的參數(shù)類型
getDeclaredConstructors()
獲取所有聲明的構(gòu)造方法
getConstructors()
獲取所有的public構(gòu)造方法
構(gòu)造方法的名稱朽色、限定符、參數(shù)组题、聲明的異常等獲取方法都與Method類似葫男,請(qǐng)參照Method
-
創(chuàng)建對(duì)象
通過(guò)反射有兩種方法可以創(chuàng)建對(duì)象:
java.lang.reflect.Constructor.newInstance()
Class.newInstance()
一般來(lái)講,我們優(yōu)先使用第一種方法崔列;那么這兩種方法有何異同呢梢褐?
-
Class.newInstance()
僅可用來(lái)調(diào)用無(wú)參的構(gòu)造方法;Constructor.newInstance()
可以調(diào)用任意參數(shù)的構(gòu)造方法赵讯。 -
Class.newInstance()
會(huì)將構(gòu)造方法中拋出的異常不作處理原樣拋出;Constructor.newInstance()
會(huì)將構(gòu)造方法中拋出的異常都包裝成InvocationTargetException
拋出利职。 -
Class.newInstance()
需要擁有構(gòu)造方法的訪問(wèn)權(quán)限;Constructor.newInstance()
可以通過(guò)setAccessible(true)
方法繞過(guò)訪問(wèn)權(quán)限訪問(wèn)private構(gòu)造方法。
例子在Method一節(jié)已經(jīng)寫(xiě)過(guò),這里直接截取過(guò)來(lái)
Class<?> c = Cat.class;
try {
Constructor constructor = c.getConstructor(String.class, int.class);
Cat cat = (Cat) constructor.newInstance( "Jack", 3);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
注意:反射不支持自動(dòng)封箱,傳入?yún)?shù)時(shí)要小心(自動(dòng)封箱是在編譯期間的叠聋,而反射在運(yùn)行期間)
數(shù)組和枚舉
數(shù)組和枚舉也是對(duì)象柒爸,但是在反射中,對(duì)數(shù)組和枚舉的創(chuàng)建西傀、訪問(wèn)和普通對(duì)象有那么一丟丟的不同斤寇,所以Java反射為數(shù)組和枚舉提供了一些特定的API接口。
數(shù)組
-
數(shù)組類型
數(shù)組類型:數(shù)組本質(zhì)是一個(gè)對(duì)象拥褂,所以它也有自己的類型娘锁。
例如對(duì)于int[] intArray
,數(shù)組類型為class [I
饺鹃。數(shù)組類型中的[
個(gè)數(shù)代表數(shù)組的維度莫秆,例如[
代表一維數(shù)組,[[
代表二維數(shù)組悔详;[
后面的字母代表數(shù)組元素類型镊屎,I
代表int
,一般為類型的首字母大寫(xiě)(long類型例外茄螃,為J)缝驳。
class [B //byte類型一維數(shù)組
class [S //short類型一維數(shù)組
class [I //int類型一維數(shù)組
class [C //char類型一維數(shù)組
class [J //long類型一維數(shù)組,J代表long類型归苍,因?yàn)長(zhǎng)被引用對(duì)象類型占用了
class [F //float類型一維數(shù)組
class [D //double類型一維數(shù)組
class [Lcom.dada.Season //引用類型一維數(shù)組
class [[Ljava.lang.String //引用類型二維數(shù)組
//獲取一個(gè)變量的類型
Class<?> c = field.getType();
//判斷該變量是否為數(shù)組
if (c.isArray()) {
//獲取數(shù)組的元素類型
c.getComponentType()
}
-
創(chuàng)建和初始化數(shù)組
Java反射為我們提供了java.lang.reflect.Array
類用來(lái)創(chuàng)建和初始化數(shù)組用狱。
//創(chuàng)建數(shù)組, 參數(shù)componentType為數(shù)組元素的類型拼弃,后面不定項(xiàng)參數(shù)的個(gè)數(shù)代表數(shù)組的維度夏伊,參數(shù)值為數(shù)組長(zhǎng)度
Array.newInstance(Class<?> componentType, int... dimensions)
//設(shè)置數(shù)組值,array為數(shù)組對(duì)象肴敛,index為數(shù)組的下標(biāo)署海,value為需要設(shè)置的值
Array.set(Object array, int index, int value)
//獲取數(shù)組的值吗购,array為數(shù)組對(duì)象,index為數(shù)組的下標(biāo)
Array.get(Object array, int index)
例子,用反射創(chuàng)建 int[] array = new int[]{1, 2}
Object array = Array.newInstance(int.class, 2);
Array.setInt(array , 0, 1);
Array.setInt(array , 1, 2);
注意:反射支持對(duì)數(shù)據(jù)自動(dòng)加寬砸狞,但不允許數(shù)據(jù)narrowing(變窄?真難翻譯)捻勉。意思是對(duì)于上述set方法,你可以在int類型數(shù)組中 set short類型數(shù)據(jù)刀森,但不可以set long類型數(shù)據(jù)踱启,否則會(huì)報(bào)IllegalArgumentException。
-
多維數(shù)組
Java反射沒(méi)有提供能夠直接訪問(wèn)多維數(shù)組元素的API研底,但你可以把多維數(shù)組當(dāng)成數(shù)組的數(shù)組處理埠偿。
Object matrix = Array.newInstance(int.class, 2, 2);
Object row0 = Array.get(matrix, 0);
Object row1 = Array.get(matrix, 1);
Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);
或者
Object matrix = Array.newInstance(int.class, 2);
Object row0 = Array.newInstance(int.class, 2);
Object row1 = Array.newInstance(int.class, 2);
Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);
Array.set(matrix, 0, row0);
Array.set(matrix, 1, row1);
枚舉
枚舉隱式繼承自java.lang.Enum
,Enum繼承自O(shè)bject榜晦,所以枚舉本質(zhì)也是一個(gè)類冠蒋,也可以有成員變量,構(gòu)造方法乾胶,方法等抖剿;對(duì)于普通類所能使用的反射方法,枚舉都能使用识窿;另外java反射額外提供了幾個(gè)方法為枚舉服務(wù)斩郎。
Class.isEnum()
Indicates whether this class represents an enum type
Class.getEnumConstants()
Retrieves the list of enum constants defined by the enum in the order they're declared
java.lang.reflect.Field.isEnumConstant()
Indicates whether this field represents an element of an enumerated type
反射的缺點(diǎn)
沒(méi)有任何一項(xiàng)技術(shù)是十全十美的,Java反射擁有強(qiáng)大功能的同時(shí)也帶來(lái)了一些副作用喻频。
-
性能開(kāi)銷
反射涉及類型動(dòng)態(tài)解析缩宜,所以JVM無(wú)法對(duì)這些代碼進(jìn)行優(yōu)化。因此甥温,反射操作的效率要比那些非反射操作低得多锻煌。我們應(yīng)該避免在經(jīng)常被執(zhí)行的代碼或?qū)π阅芤蠛芨叩某绦蛑惺褂梅瓷洹?/li> -
安全限制
使用反射技術(shù)要求程序必須在一個(gè)沒(méi)有安全限制的環(huán)境中運(yùn)行。如果一個(gè)程序必須在有安全限制的環(huán)境中運(yùn)行窿侈,如Applet炼幔,那么這就是個(gè)問(wèn)題了。 -
內(nèi)部曝光
由于反射允許代碼執(zhí)行一些在正常情況下不被允許的操作(比如訪問(wèn)私有的屬性和方法)史简,所以使用反射可能會(huì)導(dǎo)致意料之外的副作用--代碼有功能上的錯(cuò)誤乃秀,降低可移植性。反射代碼破壞了抽象性圆兵,因此當(dāng)平臺(tái)發(fā)生改變的時(shí)候跺讯,代碼的行為就有可能也隨著變化。
使用反射的一個(gè)原則:如果使用常規(guī)方法能夠?qū)崿F(xiàn)殉农,那么就不要用反射刀脏。
作者:ming152
鏈接:http://www.reibang.com/p/607ff4e79a13
來(lái)源:簡(jiǎn)書(shū)