1. 程式人生 > >Java反射機制你只要看這一篇就夠了

Java反射機制你只要看這一篇就夠了

今天來總結一下Java反射機制,在此之前,回顧下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. 物件名.gerClass

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

補充: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. 操作第二步取得的方法或是屬性

那麼反射到底有什麼用?

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

我有一個微信公眾號,經常會分享一些Java技術相關的乾貨;如果你喜歡我的分享,可以用微信搜尋“Java團長”或者“javatuanzhang”關注。