Java反射機制那些事

前言

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

在此以前,回顧下java程序的編譯運行過程,分爲三個階段:源碼(.java文件)進過編譯生成字節碼文件(.class文件),而後jvm加載字節碼文件執行程序(runtime)。bash

前兩個步驟(編譯階段)是在硬盤上完成的,後一個步驟(運行階段)是在內存中完成的,而中間這個銜接就是:jvm經過類加載器----ClassLoader把硬盤中的class文件加載到內存中生成一個Class類的對象,這樣就能夠使用這個類中的成員變量和方法。一個類默認只會被加載一次,因此這個類對應的Class對象有且僅有一個。框架

什麼是java反射機制?

1983年Smith首次提出反射這個概念,主要指程序能夠訪問、檢測和修改他自己狀態或行爲的一種能力。jvm

java反射機制是在運行狀態中中對類進行解剖並操做類中的構造方法,成員方法,成員屬性(主要用於框架中),這種動態獲取信息以及動態調用對象的方法的功能稱爲java語言的反射機制。ide

Class對象和反射機制的聯繫。

瞭解了反射機制的概念,那麼可見要想利用java反射機制作一些事,那麼就要利用Class對象,因此說Class對象是反射的前提。學習

那麼,怎麼獲取Class對象?ui

java中有三種方式獲取Class對象:

  1. 類名.classthis

  2. 對象名.getClassspa

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

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

那麼反射到底有什麼用?

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

相關文章
相關標籤/搜索