Java反射機制那些事

打雜匠發表於2019-02-15

前言

前不久學習了反射機制,來總結下。

在此之前,回顧下java程式的編譯執行過程,分為三個階段:原始碼(.java檔案)進過編譯生成位元組碼檔案(.class檔案),然後jvm載入位元組碼檔案執行程式(runtime)。

前兩個步驟(編譯階段)是在硬碟上完成的,後一個步驟(執行階段)是在記憶體中完成的,而中間這個銜接就是:jvm通過類載入器----ClassLoader把硬碟中的class檔案載入到記憶體中生成一個Class類的物件,這樣就可以使用這個類中的成員變數和方法。一個類預設只會被載入一次,所以這個類對應的Class物件有且僅有一個。

什麼是java反射機制?

1983年Smith首次提出反射這個概念,主要指程式可以訪問、檢測和修改他本身狀態或行為的一種能力。

java反射機制是在執行狀態中中對類進行解剖並操作類中的構造方法,成員方法,成員屬性(主要用於框架中),這種動態獲取資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。

Class物件和反射機制的聯絡。

瞭解了反射機制的概念,那麼可見要想利用java反射機制做一些事,那麼就要利用Class物件,所以說Class物件是反射的前提。

那麼,怎麼獲取Class物件?

java中有三種方式獲取Class物件:

  1. 類名.class

  2. 物件名.getClass

  3. Class.forName("全限定名(包名 + 類名)");

img

補充:Class物件分兩種

1.普通Class物件:基於 引用型別

2.預定義(在jvm中的)Class物件:基於 基本型別 和 void

反射機制的幾種作用:

  1. 在執行時判斷任意一個物件所屬的類
  2. 在執行時構造任意一個類的物件
  3. 在執行時判斷任意一個類所具有的成員變數和方法
  4. 在執行時呼叫任意一個物件的方法

先準備一個類:

package com.test.demo;

public class Student {
    public String name;
    private int age;

    public Student() {
    }

    private Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void show(String msg){
        System.out.println("show方法 = " + msg);
    }
    private void speak(String msg,int number){
        System.out.println("speak方法 = " + msg +":"+ number );
    }

@Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
複製程式碼

反射的使用1:構造器(Constructor)的反射

再次之前,我們可以通過公共的空參構造new一個Student,但是無法new私有的滿參構造。

Student student = new Student();
複製程式碼

現在來反射構造構造器(反射的形式建立例項)

public static void main(String[] args)
            throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, InstantiationException {
        //獲取Class物件
        Class<?> clazz = Student.class;
        /*
            根據引數型別獲取相應的構造器
            引數型別是形參型別
         */
        Constructor<?> constructor = clazz.getConstructor();
        /*
            建立例項
            引數型別是實參型別(形參一一對應)
         */
        Object obj = constructor.newInstance();
        System.out.println("obj = " + obj);
}
複製程式碼

這樣獲取到的Student物件和new出來的空參構造器new出來的物件效果一樣的(實際業務開發並沒有意義)。

前者通過new建立出來物件的方式相比用反射建立的物件更被動,前者 是被new出來的,而用反射,是自己建立自己(物件),構造方法反客為主。

還有一種方式,就是直接通過Class物件建立構造器:

public static void main(String[] args)
            throws  IllegalAccessException, InstantiationException {
        //獲取Class物件
        Class<?> clazz = Student.class;
        /*
            預設呼叫空參構造建立一個例項
            jdk9中已過時
        */
        Object obj = clazz.newInstance();
        System.out.println("obj = " + obj);
    }
複製程式碼

在Student類中 ,還有一個私有的構造器,正常方式下是不能通過私有構造器建立物件的。,但是反射可以做到:

public static void main(String[] args)
            throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, InstantiationException {
        //獲取Class物件
        Class<?> clazz = Student.class;
        /*
            獲取構造
            因為許可權是私有,但getConstructor()只能獲取public修飾的方法
            getDeclaredConstructor():獲取宣告的方法。只要宣告的就可以
         */
       Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
       System.out.println("滿參私有構造 :" + constructor);
        /*
            私有構造,newInstance會產生非法訪問異常:java.lang.IllegalAccessException
            所以要改變許可權setAccessible() -->暴力反射
         */
        constructor.setAccessible(true);
       Object obj = constructor.newInstance("小明",20);

        System.out.println("obj = " + obj);
    }
複製程式碼

以上就是利用反射來建立一個物件(反射構造器)。

反射的使用2:方法(Method)的反射

接下來看看Student物件內兩個方法的反射

我們之前(外部)使用方法,都是都是通過物件呼叫(非私有)方法,如果是靜態方法就是類直接呼叫。

那麼,使用反射呼叫(非私有)方法,該怎麼做?

public static void main(String[] args)
            throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException {
        //獲取Class物件
        Student student = new Student();
        Class<? extends Student> clazz = student.getClass();
        /*
            getMethod():獲取Class物件裡的方法
            引數一:方法名
            引數二:引數列表型別
         */
        Method show = clazz.getMethod("show", String.class);
        /*
            呼叫show方法需要物件和引數
            invoke()方法:呼叫的意思
            引數一:呼叫此方法的物件
            引數二:呼叫此方法需要傳入的實參
         */
        show.invoke(student, "hello public show");
    }
複製程式碼

反射可以理解為語言語法上的倒裝句:

我們平時寫程式碼都是我(物件)去呼叫方法,這裡就是:

new Student().show("物件呼叫方法");

而在 show.invoke(student, "hello public show"); 中,

show方法考慮的是誰來呼叫我,然後Student物件說,我來呼叫你(student作為引數)。

擴充套件:如果公共的show方法加上static關鍵字,會影響方法呼叫嗎?

提示:靜態與物件無關.

答:加上static關鍵字,普通程式碼即使不new物件也可以呼叫,這個大家都知道,那麼,在show.invoke(student, "hello public show"); 中引數1 寫 null 也是不影響的,因為,show方法來自於 Student的Class物件。

接下來看看私有方法的反射如何實現?

ps: 反射通道的API都很有規律,可讀性很強

public static void main(String[] args)
            throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException {
         //獲取Class物件
        Student student = new Student();
        Class<? extends Student> clazz = student.getClass();
        /*
            getDeclaredMethod():獲取Class物件裡的宣告過的方法(包括)
            引數一:方法名
            引數二:引數列表型別
         */
        Method speak = clazz.getDeclaredMethod("speak", String.class, int.class);
        //私有方法,暴力反射
        speak.setAccessible(true);
        /*
            呼叫show方法需要物件和引數
            invoke()方法:呼叫的意思
            引數一:呼叫此方法的物件
            引數二:呼叫此方法需要傳入的實參
         */
        speak.invoke(student, "hello private speak",2018);
    }
複製程式碼

反射的使用3:屬性(Field)的反射

在Student實體中有一個共有屬性一個私有屬性,我們可以通過物件來設定共有屬性的值,那麼通過反射如何實現所有屬性的賦值?

先來看看共有屬性name的賦值

public static void main(String[] args)
            throws ClassNotFoundException, NoSuchFieldException,
            IllegalAccessException, InstantiationException {
         //獲取Class物件,引數全限定名
        Class<?> clazz = Class.forName("com.test.demo.Student");
        /*
            getField():通過屬性名獲取屬性
         */
        Field name = clazz.getField("name");
        //獲取物件
        Object obj = clazz.newInstance();
        /*
            設定一個值
            引數一:哪個物件的屬性值
            引數二:引數
         */
        name.set(obj,"張三");
        System.out.println(obj);
    }
複製程式碼

根據前面說的API,反射屬性不難理解。

私有屬性的反射也不難實現

public static void main(String[] args)
            throws ClassNotFoundException, NoSuchFieldException,
            IllegalAccessException, InstantiationException {
         //獲取Class物件,引數全限定名
        Class<?> clazz = Class.forName("com.test.demo.Student");
        /*
            getDeclaredField():通過屬性名獲取(所有許可權)屬性
         */
        Field age = clazz.getDeclaredField("age");
        //暴力反射
        age.setAccessible(true);
        //建立物件
        Object obj = clazz.newInstance();
        /*
            設定一個值
            引數一:哪個物件的屬性值
            引數二:引數
         */
        age.set(obj,20);
        System.out.println(obj);
    }
複製程式碼

總結:

使用java的反射機制,一般需要遵循三步:

  1. 獲得你想操作類的Class物件
  2. 通過第一步獲得的Class物件去取得操作類的方法或是屬性名
  3. 操作第二步取得的方法或是屬性

那麼反射到底有什麼用?

反射最主要還是運用在框架中,瞭解反射(不止反射)才更好的瞭解一些框架的原理。

相關文章