一宪肖、背景
要理解反射永淌,首先要知道它產(chǎn)生的背景。
在 Java 中见秤,正常情況下我們只需要 new 某個(gè)類來(lái)使用就行了砂竖,但是如果想在運(yùn)行時(shí)靈活創(chuàng)建某個(gè)類怎么辦?想要使用某個(gè)類但是并沒(méi)有被 JVM 加載怎么辦鹃答?
答案就是利用 反射乎澄,這個(gè)機(jī)制可以幫助我們?cè)谶\(yùn)行期需要的時(shí)候去加載創(chuàng)建某個(gè)類,從而使用該類的方法测摔。
這里引用 知乎Kira 的回答可能更容易理解:
假如你寫(xiě)了一段代碼:Object o=new Object();運(yùn)行了起來(lái)三圆!
首先JVM會(huì)啟動(dòng),你的代碼會(huì)編譯成一個(gè).class文件避咆,然后被類加載器加載進(jìn)jvm的內(nèi)存中舟肉,你的類Object加載到方法區(qū)中,創(chuàng)建了Object類的class對(duì)象到堆中查库,注意這個(gè)不是new出來(lái)的對(duì)象路媚,而是類的類型對(duì)象,每個(gè)類只有一個(gè)class對(duì)象樊销,作為方法區(qū)類的數(shù)據(jù)結(jié)構(gòu)的接口整慎。
jvm創(chuàng)建對(duì)象前,會(huì)先檢查類是否加載围苫,尋找類對(duì)應(yīng)的class對(duì)象裤园,若加載好,則為你的對(duì)象分配內(nèi)存剂府,初始化也就是代碼:new Object()拧揽。
上面的流程就是你自己寫(xiě)好的代碼扔給jvm去跑,跑完就over了,jvm關(guān)閉淤袜,你的程序也停止了痒谴。
相當(dāng)于這些代碼都是寫(xiě) "死" 的,但是在運(yùn)行時(shí)要使用則需要利用反射通過(guò)包名靈活地加載某些類铡羡。
二积蔚、反射的組件
實(shí)現(xiàn)反射的一些主要類都處于 java.lang
包下:
- Class:代表一個(gè)類;
- Field:代表類的成員變量(屬性)烦周;
- Method:代表類的方法尽爆;
- Constructor:代表構(gòu)造器(構(gòu)造函數(shù)、構(gòu)造方法)读慎;
- Array:提供動(dòng)態(tài)創(chuàng)建數(shù)組漱贱,以及訪問(wèn)數(shù)組元素的方法;
- Package:存放路徑贪壳,包名饱亿;
接下來(lái)看一下怎么獲取這些組件蚜退。
類對(duì)象 Class
- 通過(guò)類型獲热蜓ァ;
Class c = String.class;
- 通過(guò)對(duì)象獲茸曜ⅰ:
Class c = o.getClass();
名稱 | 作用 |
---|---|
getName() | 返回String形式的該類的名稱蚂且。 |
newInstance() | 根據(jù)某個(gè)Class對(duì)象產(chǎn)生其對(duì)應(yīng)類的實(shí)例,它調(diào)用的是此類的默認(rèn)構(gòu)造方法(沒(méi)有默認(rèn)無(wú)參構(gòu)造器會(huì)報(bào)錯(cuò)) |
getClassLoader() | 返回該Class對(duì)象對(duì)應(yīng)的類的類加載器幅恋。 |
getSuperClass() | 返回某子類所對(duì)應(yīng)的直接父類所對(duì)應(yīng)的Class對(duì)象 |
isArray() | 判定此Class對(duì)象所對(duì)應(yīng)的是否是一個(gè)數(shù)組對(duì)象 |
getComponentType() | 如果當(dāng)前類表示一個(gè)數(shù)組杏死,則返回表示該數(shù)組組件的 Class 對(duì)象,否則返回 null捆交。 |
getConstructor(Class[]) | 返回當(dāng)前 Class 對(duì)象表示的類的指定的公有構(gòu)造子對(duì)象淑翼。 |
getConstructors() | 返回當(dāng)前 Class 對(duì)象表示的類的所有公有構(gòu)造子對(duì)象數(shù)組。 |
getDeclaredConstructor(Class[]) | 返回當(dāng)前 Class 對(duì)象表示的類的指定已說(shuō)明的一個(gè)構(gòu)造子對(duì)象品追。 |
getDeclaredConstructors() | 返回當(dāng)前 Class 對(duì)象表示的類的所有已說(shuō)明的構(gòu)造子對(duì)象數(shù)組玄括。 |
getDeclaredField(String) | 返回當(dāng)前 Class 對(duì)象表示的類或接口的指定已說(shuō)明的一個(gè)域?qū)ο蟆?/td> |
getDeclaredFields() | 返回當(dāng)前 Class 對(duì)象表示的類或接口的所有已說(shuō)明的域?qū)ο髷?shù)組。 |
getDeclaredMethod(String, Class[]) | 返回當(dāng)前 Class 對(duì)象表示的類或接口的指定已說(shuō)明的一個(gè)方法對(duì)象肉瓦。 |
getDeclaredMethods() | 返回 Class 對(duì)象表示的類或接口的所有已說(shuō)明的方法數(shù)組遭京。 |
getField(String) | 返回當(dāng)前 Class 對(duì)象表示的類或接口的指定的公有成員域?qū)ο蟆?/td> |
getFields() | 返回當(dāng)前 Class 對(duì)象表示的類或接口的所有可訪問(wèn)的公有域?qū)ο髷?shù)組。 |
getInterfaces() | 返回當(dāng)前對(duì)象表示的類或接口實(shí)現(xiàn)的接口泞莉。 |
getMethod(String, Class[]) | 返回當(dāng)前 Class 對(duì)象表示的類或接口的指定的公有成員方法對(duì)象哪雕。 |
getMethods() | 返回當(dāng)前 Class 對(duì)象表示的類或接口的所有公有成員方法對(duì)象數(shù)組,包括已聲明的和從父類繼承的方法鲫趁。 |
isInstance(Object) | 此方法是 Java 語(yǔ)言 instanceof 操作的動(dòng)態(tài)等價(jià)方法斯嚎。 |
isInterface() | 判定指定的 Class 對(duì)象是否表示一個(gè)接口類型 |
isPrimitive() | 判定指定的 Class 對(duì)象是否表示一個(gè) Java 的基類型。 |
newInstance() | 創(chuàng)建類的新實(shí)例 |
構(gòu)造器 Constructor
首先使用 Class
對(duì)象獲取 Constructor
:
- getConstructors()
- getConstructor(Class<?>…parameterTypes)
- getDeclaredConstructors()
- getDeclaredConstructor(Class<?>...parameterTypes)
然后就可以使用 Constructor
的下列方法:
名稱 | 作用 |
---|---|
isVarArgs() | 查看該構(gòu)造方法是否允許帶可變數(shù)量的參數(shù),如果允許孝扛,返回 true列吼,否則返回false |
getParameterTypes() | 按照聲明順序以 Class 數(shù)組的形式獲取該構(gòu)造方法各個(gè)參數(shù)的類型 |
getExceptionTypes() | 以 Class 數(shù)組的形式獲取該構(gòu)造方法可能拋出的異常類型 |
newInstance(Object … initargs) | 通過(guò)該構(gòu)造方法利用指定參數(shù)創(chuàng)建一個(gè)該類型的對(duì)象,如果未設(shè)置參數(shù)則表示采用默認(rèn)無(wú)參的構(gòu)造方法 |
setAccessiable(boolean flag) | 如果該構(gòu)造方法的權(quán)限為 private苦始,默認(rèn)為不允許通過(guò)反射利用 netlnstance()方法創(chuàng)建對(duì)象寞钥。如果先執(zhí)行該方法,并將入口參數(shù)設(shè)置為 true陌选,則允許創(chuàng)建對(duì)象 |
getModifiers() | 獲得可以解析出該構(gòu)造方法所采用修飾符的整數(shù) |
Modifier 用來(lái)獲取修飾符信息理郑,比如 isStatic(int mod)
返回是否靜態(tài)、isPublic(int mod)
是否公共咨油。不只是構(gòu)造器類型含有此方法您炉,函數(shù)、屬性都有此方法役电。
方法 Method
名稱 | 作用 |
---|---|
getName() | 獲取該方法的名稱 |
getParameterType() | 按照聲明順序以 Class 數(shù)組的形式返回該方法各個(gè)參數(shù)的類型 |
getReturnType() | 以 Class 對(duì)象的形式獲得該方法的返回值類型 |
getExceptionTypes() | 以 Class 數(shù)組的形式獲得該方法可能拋出的異常類型 |
invoke(Object obj,Object...args) | 利用 args 參數(shù)執(zhí)行指定對(duì)象 obj 中的該方法赚爵,返回值為 Object 類型 |
isVarArgs() | 查看該方法是否允許帶有可變數(shù)量的參數(shù),如果允許返回 true法瑟,否則返回 false |
getModifiers() | 獲得可以解析出該方法所采用修飾符的整數(shù) |
屬性冀膝、字段 Field
名稱 | 作用 |
---|---|
getName() | 獲得該成員變量的名稱 |
getType() | 獲取表示該成員變量的 Class 對(duì)象 |
get(Object obj) | 獲得指定對(duì)象 obj 中成員變量的值,返回值為 Object 類型 |
set(Object obj, Object value) | 將指定對(duì)象 obj 中成員變量的值設(shè)置為 value |
getlnt(0bject obj) | 獲得指定對(duì)象 obj 中成員類型為 int 的成員變量的值 |
setlnt(0bject obj, int i) | 將指定對(duì)象 obj 中成員變量的值設(shè)置為 i |
setFloat(Object obj, float f) | 將指定對(duì)象 obj 中成員變量的值設(shè)置為 f |
getBoolean(Object obj) | 獲得指定對(duì)象 obj 中成員類型為 boolean 的成員變量的值 |
setBoolean(Object obj, boolean b) | 將指定對(duì)象 obj 中成員變量的值設(shè)置為 b |
getFloat(Object obj) | 獲得指定對(duì)象 obj 中成員類型為 float 的成員變量的值 |
setAccessible(boolean flag) | 此方法可以設(shè)置是否忽略權(quán)限直接訪問(wèn) private 等私有權(quán)限的成員變量 |
getModifiers() | 獲得可以解析出該方法所采用修飾符的整數(shù) |
數(shù)組相關(guān) Array
名稱 | 作用 |
---|---|
newInstance(Class<?> componentType, int length) | 創(chuàng)建一個(gè) componentType 類型的數(shù)組霎挟,設(shè)定長(zhǎng)度 |
getLength(Object array) | 獲取數(shù)組長(zhǎng)度 |
get(Object array, int index) | 獲取數(shù)組指定下標(biāo)位置的數(shù)據(jù) |
set(Object array, int index, Object value) | 設(shè)置數(shù)組指定下標(biāo)位置的數(shù)據(jù) |
三窝剖、使用
獲取組件
接下來(lái)寫(xiě)一個(gè)簡(jiǎn)單的小例子來(lái)看下反射的使用,首先準(zhǔn)備一個(gè) Student
類:
package com.sky.test.ref;
public class Student {
private int privateAge;
public int publicAge;
public void publicStudy(){
}
private void privateStudy(){
}
}
很簡(jiǎn)單的類酥夭,擁有一個(gè)私有赐纱、一個(gè)公共屬性,一個(gè)私有熬北、一個(gè)公共方法疙描,接下來(lái)使用反射獲取這些信息。
public static void main(String[] args) {
try {
// 根據(jù)包名找到類型
Class<?> classType = Class.forName("com.sky.test.ref.Student");
// 獲取所有屬性
Field[] fields = classType.getFields();
Field[] fieldsD = classType.getDeclaredFields();
for (Field f : fields) {
System.out.println("getFields 獲取到的屬性:" + f.getName());
// getFields 獲取到的屬性:publicAge
}
for (Field f : fieldsD) {
System.out.println("getDeclaredFields 獲取到的屬性:" + f.getName());
// getDeclaredFields 獲取到的屬性:privateAge
// getDeclaredFields 獲取到的屬性:publicAge
}
// 獲取該類的所有方法
Method[] methods = classType.getMethods();
Method[] methodsD = classType.getDeclaredMethods();
for (Method m : methods) {
System.out.println("getMethods 獲取到的方法:" + m.getName());
// getMethods 獲取到的方法:publicStudy
// getMethods 獲取到的方法:wait
// getMethods 獲取到的方法:wait
// getMethods 獲取到的方法:wait
// getMethods 獲取到的方法:equals
// getMethods 獲取到的方法:toString
// getMethods 獲取到的方法:hashCode
// getMethods 獲取到的方法:getClass
// getMethods 獲取到的方法:notify
// getMethods 獲取到的方法:notifyAll
}
for (Method m : methodsD) {
System.out.println("getDeclaredMethods 獲取到的方法:" + m.getName());
// getDeclaredMethods 獲取到的方法:publicStudy
// getDeclaredMethods 獲取到的方法:privateStudy
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
-
Class.forName()
方法傳入包名讶隐,以找到想要使用的類起胰,找不到會(huì)拋ClassNotFoundException
異常; -
Class
的getFields()
方法:獲取所有 公共的(public) 的屬性整份,包含父類的 public 屬性(但不包含父類非 public 屬性)待错; -
Class
的getDeclaredFields()
方法:獲取本類所有的屬性,但不包含父類的屬性烈评; -
Class
的getMethods()
方法:獲取所有 公共的(public) 的方法火俄,包含父類的 public 方法(但不包含父類非 public 方法); -
Class
的gettDeclaredMethods()
方法:獲取本類所有的方法讲冠,但不包含父類的方法瓜客;
簡(jiǎn)單使用
已經(jīng)成功獲取到這些方法和屬性了,接下來(lái)就是使用通過(guò)反射獲取到的屬性和方法了。但是首先要做的是能把修改的信息展示出來(lái)谱仪,把 Student
類簡(jiǎn)單修改下:
public class Student extends SuperStudent {
private int privateAge;
public int publicAge;
public void publicStudy(int hour) {
System.out.println("設(shè)置年齡為" + publicAge);
System.out.println("學(xué)習(xí)了" + hour + "小時(shí)");
}
private void privateStudy() {
}
}
只是給 publicStudy()
方法增加了 int 類型的參數(shù)玻熙,并打印了兩個(gè)字段:publicAge、hour
疯攒。接下來(lái)就可以使用反射來(lái)修改里面的信息了嗦随。
public static void main(String[] args) {
try {
// 1. 根據(jù)包名找到類型
Class<?> classType = Class.forName("com.sky.test.ref.Student");
// 創(chuàng)建實(shí)例,修改該對(duì)象的信息
Object o = classType.newInstance();
// 2. 獲取 年齡 字段
Field field = classType.getDeclaredField("publicAge");
// 設(shè)置年齡 publicAge 屬性:第一個(gè)參數(shù)為要修改的對(duì)象敬尺,第二個(gè)參數(shù)為數(shù)值
field.set(o, 1);
// 3. 獲取 publicStudy 方法枚尼,第二個(gè)參數(shù)傳入?yún)?shù)類型
Method method = classType.getDeclaredMethod("publicStudy", int.class);
// 調(diào)用 publicStudy 方法
method.invoke(o, 3);
} catch (ClassNotFoundException
| NoSuchFieldException
| NoSuchMethodException | IllegalAccessException
| InstantiationException | InvocationTargetException
e) {
e.printStackTrace();
}
}
- 首先要做的是找到該類型,并創(chuàng)建該類型的對(duì)象砂吞。修改數(shù)據(jù)首先得有對(duì)象吧署恍,然后內(nèi)存空間有它的地方吧,不然修改啥蜻直。
- 然后就是獲取字段了盯质,我們要做的是修改年齡
publicAge
字段:獲取該字段的Field
對(duì)象,使用set()
傳入 對(duì)象 和要修改的值概而,這樣就 ok 了呼巷。 - 類似的方法獲取
publicStudy()
方法,調(diào)用invoke()
方法傳參激活打印到腥。特別注意: 第二個(gè)參數(shù)是當(dāng)前方法的參數(shù)類型朵逝,如果傳的不對(duì)直接給你報(bào)NoSuchMethodException
蔚袍。而且注意int.class
和Integer.class
竟然不是一個(gè)東西乡范,傳Integer
還是會(huì)報(bào)錯(cuò)...
這樣就完成了普通的使用。
特別注意:權(quán)限問(wèn)題
反射使用引入了權(quán)限機(jī)制啤咽,私有的屬性已經(jīng)方法直接反射調(diào)用的話會(huì)拋出異常晋辆。
Field fieldPrivate = classType.getDeclaredField("privateAge");
fieldPrivate.set(o,2); // 會(huì)拋出 java.lang.IllegalAccessException 異常
如果想要使用的話,需要設(shè)置屬性或方法等的訪問(wèn)權(quán)限:
Field fieldPrivate = classType.getDeclaredField("privateAge");
fieldPrivate.setAccessible(true);
fieldPrivate.set(o,2);
注解使用
注解一般也是利用反射來(lái)使用的宇整,大概過(guò)程就是利用反射獲取 Class
類型上面的注解瓶佳,然后執(zhí)行相應(yīng)邏輯。
之前寫(xiě)過(guò)一篇文章有記錄鳞青,在此不再贅述霸饲。
遠(yuǎn)程方法運(yùn)用反射
- 服務(wù)端:HelloService
- 客戶端:SimpleClient
- 通信信息:Call
客戶端生成 Call 對(duì)象,指定要調(diào)用的類臂拓、對(duì)象以及方法參數(shù)等信息厚脉,通過(guò)流的形式發(fā)生給服務(wù)端。
public class Call implements Serializable {
private static final long serialVersionUID = 6659953547331194808L;
private String className; // 表示類名或接口名
private String methodName; // 表示方法名
private Class[] paramTypes; // 表示方法參數(shù)類型
private Object[] params; // 表示方法參數(shù)值
// 表示方法的執(zhí)行結(jié)果
// 如果方法正常執(zhí)行胶惰,則result為方法返回值傻工,如果方法拋出異常,那么result為該異常。
private Object result;
...
}
服務(wù)端處理過(guò)將結(jié)果再以流的形式返回給客戶端中捆。
反射操作數(shù)組
需求: 創(chuàng)建一個(gè)數(shù)組鸯匹,通過(guò)反射獲取數(shù)組的類型,然后創(chuàng)建一個(gè)新長(zhǎng)度的新數(shù)組泄伪,拷貝舊數(shù)組的內(nèi)容殴蓬。
為了看出效果,首先為 Student
類增加一個(gè)靜態(tài)的創(chuàng)建方法:
public class Student extends SuperStudent {
public int publicAge;
...
public static Student newInstance(){
Student student = new Student();
student.publicAge = 8;
return student;
}
}
使用此方法創(chuàng)建實(shí)例蟋滴,給內(nèi)部的年齡字段一個(gè)初始值 8科雳。接下來(lái)就創(chuàng)建一個(gè) Student 的數(shù)組,并利用反射進(jìn)行創(chuàng)建和復(fù)制脓杉。
public static void main(String[] args) {
// 1. 創(chuàng)建數(shù)組糟秘,里面存放一個(gè) Student 對(duì)象
Student[] students = new Student[]{Student.newInstance()};
// 2. 獲取元素類型
Class<?> classType = students.getClass().getComponentType();
// 3. 創(chuàng)建新數(shù)組
Object[] objects = (Object[]) Array.newInstance(classType, Array.getLength(students));
// 4. 進(jìn)行 copy:原數(shù)組,拷貝起始球散,新數(shù)組尿赚,拷貝起始,拷貝長(zhǎng)度
System.arraycopy(students, 0, objects, 0, Array.getLength(students));
// 5. 從新數(shù)組獲取第一個(gè)元素
Student s = (Student)Array.get(objects, 0);
System.out.println("拷貝完成蕉堰,第一個(gè)數(shù)據(jù)年齡: " + s.publicAge);
// 拷貝完成凌净,第一個(gè)數(shù)據(jù)年齡: 8
}
需要注意的是一些方法的使用:
getClass()
和getComponentType()
的區(qū)別:
getClass()
用于獲取對(duì)象的類型,比如這里獲取數(shù)組的是Student[]
類型屋讶;
getComponentType()
獲取數(shù)組元素類型冰寻,比如這里獲取到的是Student
類型。Array.newInstance()
可以幫助我們動(dòng)態(tài)創(chuàng)建數(shù)組皿渗,可以從別的地方傳遞來(lái)類型和長(zhǎng)度斩芭,即可完成創(chuàng)建。
反射操作泛型
知識(shí)儲(chǔ)備:
-
Type
:所有類型的公共父接口乐疆,Class
就是Type
的子類之一划乖,以下是該接口的子類型:-
ParameterizedType
:參數(shù)化類型,可以理解為泛型挤土; -
TypeVariable
:類型變量琴庵,也就是平時(shí)定義的T t、K k
等類似的變量仰美; -
GenericArrayType
:泛型數(shù)組類型迷殿,T[]
這種類型; -
WildcardType
:通配符類型咖杂,<?>, <? Extends String>
這種庆寺。
-
小栗子:
依舊是使用上面的 Student
類,給它增加一個(gè)包含泛型的方法:
public void setStudents(List<Student> list){
}
接下來(lái)就是獲取泛型:
public static void main(String[] args) {
try {
Method method = Student.class.getMethod("setStudents", List.class);
// 獲取方法參數(shù)類型
Type[] t = method.getGenericParameterTypes();
for (Type type : t) {
System.out.println("參數(shù)類型:" + type);// 泛型類型:java.util.List<com.sky.test.ref.Student>
if (type instanceof ParameterizedType) {
Type[] real = ((ParameterizedType) type).getActualTypeArguments();
for (Type genericType : real) {
System.out.println("泛型類型:" + genericType);
// 泛型類型:class com.sky.test.ref.Student
}
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
這只是利用反射獲取泛型的一個(gè)很小的例子翰苫,可以使用不同的方法獲取 接口止邮、類等的參數(shù)这橙、返回值中的泛型。
說(shuō)起來(lái)為什么會(huì)把泛型擦除掉导披,是因?yàn)榉盒捅緛?lái)不是 Java 中的東西(可能是抄來(lái)的)屈扎。如果要把泛型加入字節(jié)碼,就需要修改字節(jié)碼指令集撩匕。我們都知道新增簡(jiǎn)單修改難鹰晨,這字節(jié)碼指令用了多少年了,要改基本不肯能了止毕。
所以把泛型擦除之后呢模蜡,又需要一些類型來(lái)表示到底是哪種泛型,于是就有了上面那些 Type 的子接口扁凛。
四忍疾、反射影響性能
這里引用文章 大家都說(shuō) Java 反射效率低,你知道原因在哪里么 的結(jié)論:
- Method#invoke 方法會(huì)對(duì)參數(shù)做封裝和解封操作
- 需要檢查方法可見(jiàn)性
- 需要校驗(yàn)參數(shù)
- 反射方法難以內(nèi)聯(lián)
- JIT 無(wú)法優(yōu)化
具體可以參考該文章谨朝,自此本文結(jié)束卤妒。
參考: