1. 程式人生 > 實用技巧 >反射機制詳解

反射機制詳解

0x01、類的載入流程

當程式要使用某個類時,如果該類還未被載入到記憶體中,則系統會通過載入,連線,初始化三步來實現對這個類進行初始化。

①載入
就是指將class檔案讀入記憶體,併為之建立一個Class物件。
任何類被使用時系統都會建立一個Class物件。

②連線
驗證 是否有正確的內部結構,並和其他類協調一致
準備 負責為類的靜態成員分配記憶體,並設定預設初始化值 static{}語句塊
解析 將類的二進位制資料中的符號引用替換為直接引用

③初始化型別
建立類的例項
訪問類的靜態變數,或者為靜態變數賦值
呼叫類的靜態方法
使用反射方式來強制建立某個類或介面對應的java.lang.Class物件
初始化某個類的子類
直接使用java.exe命令來執行某個主類
.....

0x02.反射機制入門

通過上方閱讀,當我們的程式在執行後,第一次使用某個類的時候,會將此類的class檔案讀取到記憶體,併為此類建立一個Class物件

類的載入時機

1. 建立類的例項。
2. 類的靜態變數,或者為靜態變數賦值。
3. 類的靜態方法。
4. 使用反射方式來強制建立某個類或介面對應的java.lang.Class物件。
5. 初始化某個類的子類。
6. 直接使用java.exe命令來執行某個主類。

public class Test {
    public static void main(String[] args) throws Exception{
        // 類的載入時機
        //  1. 建立類的例項。
        //  Student stu = new Student();

        // 2. 類的靜態變數,或者為靜態變數賦值。
        // Person.country = "中國";

        // 3. 類的靜態方法。
        // Person.method();

        // 4. 使用反射方式來強制建立某個類或介面對應的java.lang.Class物件。
        // Class<?> c = Class.forName("com.itheima.demo1_類的載入.Student");

        //  5. 初始化某個類的子類。
        // Zi zi = new Zi();

        // 6. 直接使用java.exe命令來執行某個主類。
    }
}

讀取到記憶體,併為次類建立一個Class物件;這時候我們反射的內容開始了,我們第一步,就是通過反射,去獲取class物件

0x03. 獲取Class物件並建立物件

方式1: 通過`類名.class`獲得
方式2:通過`物件名.getClass()`方法獲得
方式3:通過Class類的靜態方法獲得:` static Class forName("類全名")`
    * 每一個類的Class物件都只有一個。

下面是獲得到class物件,這是反射的三種方法,最常見的,是forName獲取class物件

/*
    反射獲取class物件的三種方式
 */
public class Demo3 {
    public static void main(String[] args) throws ClassNotFoundException {

        // 1、類名.class
        Class<Student> student1 = Student.class;
        System.out.println(student1);

        // 2、物件名.getClass
        Student student = new Student();
        Class<? extends Student> student2 = student.getClass();
        System.out.println(student2);

        // 3、 Class.forName("")
        Class<?> student3 = Class.forName("com.itheima_反射.Student");
        System.out.println(student3);
    }
}

這時候我們需要使用 newInstance() 方法來建立物件

由於返回值是 OBject型別的,所以我們需要轉型

public class Demo3 {
    public static void main(String[] args) throws Exception {
        // 1、獲取Class物件
        Class<?> student = Class.forName("com.itheima_反射.Student");
        // 2、例項化物件
        Student o = (Student) student.newInstance();

    }
}

而這一段程式碼,可以變為

public class Demo3 {
    public static void main(String[] args) throws Exception {
        // 等價於  Student student = new Student();
        Student student = (Student) Class.forName("com.itheima_反射.Student").newInstance();
    }
}

0x04、操作構造方法

因為Student中是有構造方法的,而這邊newInstance是預設呼叫無參構造方法;

反射之操作構造方法的目的
    * 獲得Constructor物件來建立類的物件。

Constructor類概述
    * 類中的每一個構造方法都是一個Constructor類的物件

1. Constructor getConstructor(Class... parameterTypes)
        * 根據引數型別獲得對應的Constructor物件。
        * 只能獲得public修飾的構造方法
 2. Constructor getDeclaredConstructor(Class... parameterTypes)
        * 根據引數型別獲得對應的Constructor物件
    	* 可以是public、protected、(預設)、private修飾符的構造方法。
 3. Constructor[] getConstructors()
        獲得類中的所有構造方法物件,只能獲得public的
 4. Constructor[] getDeclaredConstructors()
        獲得類中的所有構造方法物件
    	可以是public、protected、(預設)、private修飾符的構造方法。

1. T newInstance(Object... initargs)
 	根據指定的引數建立物件
2. void setAccessible(true)
   設定"暴力反射"——是否取消許可權檢查,true取消許可權檢查,false表示不取消
import java.lang.reflect.Constructor;

public class Demo {
    public static void main(String[] args) throws Exception {
        // 1、獲取class物件
        Class<?> student = Class.forName("org.test.反射01.Student");
        // 2、獲取Student類的帶有三個引數的構造方法,並且引數的型別順序為:String,String,int
        Constructor<?> user = student.getConstructor(String.class,String.class, int.class);
        // 3、傳入引數,並使用 newInstance() 例項化物件,並向下轉型
        Student o = (Student)user.newInstance("吳迪", "男", 19);
        System.out.println(o);

    }
}

這是構造方法的public修飾的獲取,獲取private修飾的構造方法,需要先setAccessible(true),然後再執行

具體我就不細說了,畢竟用法都一樣

0x05. 操作成員方法

* Method getMethod(String name,Class...args);
      * 根據方法名和引數型別獲得對應的成員方法物件,只能獲得public的
* Method getDeclaredMethod(String name,Class...args);     掌握
       * 根據方法名和引數型別獲得對應的成員方法物件,包括public、protected、(預設)、private的
* Method[] getMethods();
        * 獲得類中的所有成員方法物件,返回陣列,只能獲得public修飾的且包含父類的
* Method[] getDeclaredMethods();    掌握
         * 獲得類中的所有成員方法物件,返回陣列,只獲得本類的,包括public、protected、(預設)、private的

Method物件常用方法
*  Object invoke(Object obj, Object... args)
    * 呼叫指定物件obj的該方法
    * args:呼叫方法時傳遞的引數
*  void setAccessible(true)
    設定"暴力訪問"——是否取消許可權檢查,true取消許可權檢查,false表示不取消

我們先看看呼叫沒有傳參的成員方法
Studeng.java檔案:

public class Student {
    public void show1(){
        System.out.println("public 修飾的show1方法,無引數...");
    }

    public void show1(String str,int num){
        System.out.println("public 修飾的show1方法,2個引數...");
        System.out.println("str:"+str+",num:"+num);
    }

    public void show2(){
        System.out.println("public 修飾的show2方法...");
    }

    private void show3(){
        System.out.println("private 修飾的show3方法...");
    }
}

我們這邊嘗試去呼叫show1方法

import java.lang.reflect.Method;

public class Demo {
    public static void main(String[] args) throws Exception {

        //獲取class物件
        Class<?> clzz = Class.forName("org.test.反射01.Student");
        //例項化物件(預設呼叫無參構造方法)
        Object o = clzz.newInstance();
        // 通過class物件,呼叫getMethod方法去獲取名為show1()的成員方法,並沒有傳參
        Method showM = clzz.getMethod("show1");
        showM.invoke(o);


    }
}

那我們再去呼叫另外一個show1()方法

import java.lang.reflect.Method;

public class Demo {
    public static void main(String[] args) throws Exception {
        //獲取class物件
        Class<?> clzz = Class.forName("org.test.反射01.Student");
        //例項化物件(預設呼叫無參構造方法)
        Object o = clzz.newInstance();
        // 通過class物件,呼叫getMethod方法去獲取名為show1()的成員方法,並沒有傳參
        Method showM = clzz.getMethod("show1",String.class,int.class);
        showM.invoke(o,"吳一凡",17);


    }
}

0x05. 操作成員變數

反射之操作成員變數的目的
    * 通過Field物件給對應的成員變數賦值和取值

Field類概述
    * 每一個成員變數都是一個Field類的物件。

通過反射獲取類的成員變數

Class類中與Field相關的方法
* Field getField(String name);
    *  根據成員變數名獲得對應Field物件,只能獲得public修飾
* Field getDeclaredField(String name);
    *  根據成員變數名獲得對應Field物件,包括public、protected、(預設)、private的
* Field[] getFields();
    * 獲得所有的成員變數對應的Field物件,只能獲得public的
* Field[] getDeclaredFields();
    * 獲得所有的成員變數對應的Field物件,包括public、protected、(預設)、private的

通過反射訪問成員變數

Field物件常用方法
void  set(Object obj, Object value) 
void setInt(Object obj, int i) 	
void setLong(Object obj, long l)
void setBoolean(Object obj, boolean z) 
void setDouble(Object obj, double d) 

Object get(Object obj)  
int	getInt(Object obj) 
long getLong(Object obj) 
boolean getBoolean(Object ob)
double getDouble(Object obj) 

void setAccessible(true);暴力反射,設定為可以直接訪問私有型別的屬性。
Class getType(); 獲取屬性的型別,返回Class物件。

setXxx方法都是給物件obj的屬性設定使用,針對不同的型別選取不同的方法。

getXxx方法是獲取物件obj對應的屬性值的,針對不同的型別選取不同的方法。

package org.test.反射01;

import java.lang.reflect.Field;

public class Demo {
    public static void main(String[] args) throws Exception {
        //獲取class物件
        Class<?> clzz = Class.forName("org.test.反射01.Student");
        //例項化物件(預設呼叫無參構造方法)
        Object stu = clzz.newInstance();
        // 通過class物件,呼叫getDeclaredField,獲取被private修飾名為sex的屬性
        Field sexF = clzz.getDeclaredField("sex");
        // 暴力反射
        sexF.setAccessible(true);
        //設定值
        sexF.set(stu,"李四");
        // 獲取
        System.out.println(sexF.get(stu));// 李四



    }
}