深入剖析JAVA的反射機(jī)制

定義

在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對象有三種方式:

  1. 使用Class類的靜態(tài)方法forName: Class.forName("java.lang.String") ,參數(shù)必須添加完整的路徑,包括包名。
  2. 使用類的.class 語法: String.class
  3. 使用對象的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é)果:

部分運(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)。
感謝支持!
                                        ---起個名忒難

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、概述 Java反射機(jī)制定義 Java反射機(jī)制是在運(yùn)行狀態(tài)中,對于任意一個類,都能夠知道這個類中的所有屬性和方法...
    CoderZS閱讀 1,704評論 0 25
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,854評論 18 399
  • 一、概述 1、Java反射機(jī)制(Java-Reflect): 在運(yùn)行狀態(tài)中,對于任意一個類,都能夠知道這個類中的所...
    年少懵懂丶流年夢閱讀 4,590評論 0 5
  • 帶節(jié)點(diǎn)進(jìn)度條的實(shí)現(xiàn)方法不止一個,但是如果要實(shí)現(xiàn)圖中這種效果的,初步看好像還不簡單。進(jìn)度條的形狀不規(guī)則、背景是漸變顏...
    NanBox閱讀 4,881評論 3 35
  • 事實(shí)證明,二年級玩奧數(shù)是可以的,請看下圖: 為什么孩子會如此興奮,因?yàn)槲覀冞@里是俱樂部,不是集訓(xùn)營! 在這里老師像...
    何立泵閱讀 307評論 0 2

友情鏈接更多精彩內(nèi)容