定義
在Java 的運(yùn)行時環(huán)境中,對于任意的一個類,都能夠知道這個類的所有屬性和方法,對于任意一個對象都能夠調(diào)用任意的方法和屬性,這種動態(tài)的獲取類的信息以及動態(tài)的調(diào)用對象方法的功能,稱之為反射(注意是運(yùn)行狀態(tài),非編譯)。
Java反射提供的主要功能:
- 在運(yùn)行時判斷任意一個對象所屬的類
- 在運(yùn)行時構(gòu)造任意一個類的對象
- 在運(yùn)行時判斷任意一個類所具有的成員變量和方法
- 在運(yùn)行時調(diào)用任意一個對象的方法
Java反射機(jī)制實(shí)現(xiàn)類:
- Field類:代表類的成員變量
- Method類:代表類的方法
- Constructor類:代表類的構(gòu)造方法
- Array類:提供動態(tài)的創(chuàng)建數(shù)組
- 以上四個類都位于java.lang.reflect 包中
- Class類:代表一個類 , 位于java.lang 包中
反射的使用
在JAVA中,無論一個類生成多少個對象,這些對象都會對應(yīng)同一個Class對象 。要想使用反射,首先需要獲得待處理的類或者對象所對應(yīng)的Class對象。該Class對象是在類被加載之后,由系統(tǒng)自動生成的。獲取該Class對象有三種方式:
- 使用Class類的靜態(tài)方法forName: Class.forName("java.lang.String") ,參數(shù)必須添加完整的路徑,包括包名。
- 使用類的.class 語法: String.class
- 使用對象的getClass()方法: String s = "reflect" ;Class<?> clazz = s.getClass()
以上便是獲取Class對象的三種方式,要使用反射,必須先獲取Class對象,一旦獲取了Class對象之后,就可以調(diào)用Class對象的方法來獲得對象和該類的真實(shí)信息。
反射相關(guān)API ,使用反射生成并操作對象
Class對象可以獲得帶操作類里的方法(Method),構(gòu)造器(constructor),F(xiàn)ield 。要操作方法需要獲得Mehtod對象,要操作構(gòu)造器需要獲得Constructor對象,要操作屬性需要獲得Field對象。
下面將會結(jié)合具體的實(shí)例,來說明生成Class對象的三種方式是如何使用的:
反射初體驗(yàn),通過第一種方式,獲取Class對象,并打印java.lang.String的所有聲明方法:
Class<?> aClass = Class.forName("java.lang.String"); //獲取String類的Class對象
Method[] methdos = aClass.getDeclaredMethods(); //獲取所有的聲明方法
for(Method m : methdos) //循環(huán)遍歷Method數(shù)組
System.out.println(m); //打印方法名稱,包含了private 方法
運(yùn)行結(jié)果:

通過反射調(diào)用某個類的方法
public class InvokeTest {
public int add(int a , int b){
return a +b ;
}
public String echo(String message){
return "Hello :" +message ;
}
public static void main(String[] args) throws Exception {
//通過new的方式調(diào)用
//InvokeTest invokeTest = new InvokeTest();
//System.out.println(invokeTest.add(1 , 2));
//System.out.println(invokeTest.echo("Tom"));
//通過反射調(diào)用方法
//通過內(nèi)置語法,獲取Class對象
Class<InvokeTest> invokeTestClass = InvokeTest.class;
//創(chuàng)建Class對象表示的類的實(shí)例 ,調(diào)用Class對象的newInstance()方法
InvokeTest invokeTest = invokeTestClass.newInstance();
//獲取待操作類的Method對象。傳入方法名字,和方法的參數(shù)類型
Method add = invokeTestClass.getDeclaredMethod("add", new Class[]{int.class, int.class});
//使用這種方式也是可以得,因?yàn)間etDeclaredMethod()第二個參數(shù)是一個可變參數(shù)
//Method add = InvokeTestClass.getDeclaredMethod("add" , int.class , int.class)
Method echo = invokeTestClass.getDeclaredMethod("echo", new Class[]{String.class});
//調(diào)用Method對象的invoke方法,表示含義為,調(diào)用invokeTest對象的add/echo方法,并傳出參數(shù)值
Object addResult = add.invoke(invokeTest, new Object[]{1, 2});
Object echoResult = echo.invoke(invokeTest, new Object[]{"Tome"});
//打印輸出的結(jié)果
System.out.println((Integer) addResult);
System.out.println((String) echoResult);
//運(yùn)行結(jié)果
//3
//Hello :Tome
}
}
通過反射調(diào)用某個類的構(gòu)造方法 ,創(chuàng)建對象
public class ConstructorTest {
public static void main(String[] args) throws Exception {
User user = new User() ;
//使用第三種獲取Class對象的方式。使用getClass()方法
Class<?> userClass = user.getClass();
//調(diào)用無參構(gòu)造方法,創(chuàng)建對象實(shí)例,反射無法得知返回的類型,需要強(qiáng)制類型轉(zhuǎn)換
Constructor<?> constructor = userClass.getConstructor();
User user1 = (User)constructor.newInstance();
//上面兩行代碼可以替換為下面一行代碼
//User u = (User)userClass.newInstance();
user1.setAge(18);
user1.setName("zhangsan");
System.out.println(user1.getName() +"," + user1.getAge()); //zhangsan,18
System.out.println("--------------------------");
//調(diào)用有參構(gòu)造方法,下面兩行方法中的參數(shù)要一一對應(yīng)
Constructor<?> constructor1 = userClass.getConstructor(new Class[]{String.class, int.class});
User user2 = (User)constructor1.newInstance(new Object[]{"lisi", 30});
System.out.println(user2.getName() +"," +user2.getAge());
}
}
class User{
private String name ;
private int age ;
public User(){}
public User(String name , int age ){
this.name = name ;
this.age = age ;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
通過反射生成對象有兩種方式:
1.使用Class對象的newInstance()方法,這種方法要求對象的對應(yīng)類有無參構(gòu)造方法
2.先使用Class對象獲得Constructor()對象,在調(diào)用Constructor對象的newInstance()方法。這種方式可以調(diào)用無參的構(gòu)造方法,也可以調(diào)用有參的構(gòu)造方法。但是Constructor()和newInstance()中的參數(shù)要一一對應(yīng)。可參考上例。
通過反射操作類的屬性
public class FieldTest {
public static void main(String[] args) throws Exception {
//獲取Class對象,并生成實(shí)例對象
Class<?> personClass = Person.class;
Object p = personClass.newInstance();
//通過Class對象的getDeclaredField()方法,獲取類的field
Field name = personClass.getDeclaredField("name");
Field age = personClass.getDeclaredField("age");
//name字段為private 修飾,使用setAccessible()方法,取消訪問權(quán)限檢查
name.setAccessible(true);
//調(diào)用set()方法,設(shè)置p對象的值
name.set(p , "zhangsan");
age.set(p , 18);
//打印值
System.out.println(p); //name: zhangsan, age: 18
}
}
class Person{
private String name ;
public int age ;
@Override
public String toString() {
return "name: "+name +", age: "+age ;
}
}
通過setAccessible()方法,可以通過反射訪問類中的私有屬性和私有方法,通常不建議這樣使用,因?yàn)樗蚱屏朔庋b的特性。但是在一些特定情況中,還是需要使用這種方式的。
通過反射操作數(shù)組
public class ArrayTest {
public static void main(String[] args) {
//創(chuàng)建一個類型為String, 長度為10 的數(shù)組
Object o = Array.newInstance(String.class, 10);
//為數(shù)組賦值
Array.set(o , 2 ,"hello");
Array.set(o , 5 ,"world");
//獲取數(shù)組的值,并打印
System.out.println(Array.get(o , 2)); //hello
System.out.println(Array.get(o , 5)); //world
}
}
上面簡單介紹了幾個常用的方法,旨在說明反射對方法,構(gòu)造方法,字段,數(shù)組中的調(diào)用方式,除了上面的這些,還可以獲取父類,接口,包 ,全部的方法聲明,全部的field聲明等與類相關(guān)的全部信息。想要了解更多內(nèi)容,請自行查閱API。
通過反射生成動態(tài)代理
先看代碼,稍后再解釋動態(tài)代理的實(shí)現(xiàn)過程,這個地方可能有點(diǎn)難理解,多看幾遍就可以了。
/*定義接口*/
public interface Subject {
public void sayHello(String name);
}
/*定義接口的實(shí)現(xiàn)類,也就是真實(shí)的代理調(diào)用對象*/
public class RealSubject implements Subject{
public void sayHello(String name ) {
System.out.println("hello : " + name );
}
}
/*定義動態(tài)代理類*/
public class DynamicProxy implements InvocationHandler{
private Object obj = null ; //此處定義了Object類型,表示可以代理任何對象
public DynamicProxy(Object obj){
this.obj = obj ;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
//調(diào)用obj對象的method方法,并傳入?yún)?shù)args,本例中相當(dāng)于調(diào)用obj對象的sayHello()方法,并傳入Tom 參數(shù)
method.invoke(obj , args) ;
after();
return null ; //Subject接口中方法沒有返回值,返回Null
}
//此方法表示代理的額外操作。
private void before(){
System.out.println("before proxy");
}
//此方法表示代理的額外操作
private void after(){
System.out.println("after proxy");
}
}
客戶端調(diào)用:
public class Client {
public static void main(String[] args) {
//需要代理的真實(shí)對象
RealSubject realSubject = new RealSubject() ;
//創(chuàng)建代理類,并將真實(shí)的代理對象,傳入構(gòu)造方法中
InvocationHandler handler = new DynamicProxy(realSubject);
//運(yùn)行時動態(tài)生成的代理實(shí)例,實(shí)現(xiàn)了RelSubject類所實(shí)現(xiàn)的接口,所以可以強(qiáng)制轉(zhuǎn)換為Subject類型,第一個參數(shù)是類加載器,第二個參數(shù)是代理類實(shí)現(xiàn)的接口,第三個參數(shù)是處理實(shí)際工作的handler實(shí)例
Subject subject = (Subject)Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
//調(diào)用接口中聲明的方法 ,流程跳轉(zhuǎn)到handler的invoke()方法中執(zhí)行。
subject.sayHello("Tom");
}
}
總結(jié):
Java的動態(tài)代理類位于java.lang.reflect包下,涉及到兩個類:
interface InvocationHandler: 該接口中只定義了一個方法-invoke(),使用動態(tài)代理類時,必須實(shí)現(xiàn)這個接口。
Porxy:該類即為動態(tài)代理類,常用newProxyInstance()來生成代理實(shí)例,它不會做什么實(shí)質(zhì)性的工作,實(shí)質(zhì)性的工作是由與之關(guān)聯(lián)的InvocationHandle來處理的。也就是說生成的代理對象都有一個與之關(guān)聯(lián)的InvocationHandler對象。
動態(tài)代理的使用步驟:
- 創(chuàng)建被代理類的接口和實(shí)現(xiàn)類,因?yàn)閯討B(tài)代理是基于接口的。這也是動態(tài)代理的一個小小缺憾。
- 創(chuàng)建實(shí)現(xiàn)InvocationHandler接口的類,并實(shí)現(xiàn)invoke()方法
- 通過Proxy的靜態(tài)方法newProxyInstance(ClassLoader loader ,Class[] interfaces ,InvocationHandler handler) 創(chuàng)建一個代理實(shí)例。
- 通過上面創(chuàng)建的代理實(shí)例,調(diào)用方法
至此,End!
少年聽雨歌樓上,紅燭昏羅帳。
壯年聽雨客舟中,江闊云低,斷雁叫西風(fēng)。
感謝支持!
---起個名忒難