1. 程式人生 > >Java反射:如何正確理解,不如手寫一個(反射包分析、樓主親測)

Java反射:如何正確理解,不如手寫一個(反射包分析、樓主親測)

Java反射機制、動態代理是基於什麼原理?

這個問題可謂是老生常談的一個熱門問題了,如果沒有深入的思考還真的是很難回到上來。那麼今天我們一起來看看,如何正確清晰的認識這個熱門卻又說簡單又不簡單說複雜又比較複雜的問題。

一、什麼是反射

反射機制是Java語言提供的一種基礎功能

這個功能能夠賦予程式在執行時進行自省的能力。

通過反射我們可以直接操作類或者物件,比如:獲取某個物件的類的定義、獲取類宣告的屬性和方法、呼叫方法或者構造物件,甚至可以執行時修改類的定義。

二、認識反射的包

1、反射包

Java為我們準備一個專門的反射包java.lang.reflect

這裡寫圖片描述

在這個包中中有3個類,FieldMethodConstructor分別作用於類的域(成員變數)、方法和構造器。

2、AccessibleObject

關於反射有一點需要特意注意一下,就是反射包提供的AccessibleObject類中的setAccessible方法。

這裡寫圖片描述

他的子類很多也重寫了這個方法,這裡的所謂的setAccessible顧名思義,就是指的是成員變數前面的用於修飾的publicprotectedprivate,這個方法也就意味著我們可以在執行時通過反射去修改類的成員變數的訪問限制。

setAccessible的應用場景非常廣泛,各種框架:開發、測試、依賴注入。例如在資料庫O/R Mapping

框架中,我們在載入或者持久化資料的時候,框架通常會利用反射做這個事情,而不需要開發者自己去實現。

還有個典型的應用場景,就是繞過API的訪問控制。我們在開發的過程中,有時候,可能需要呼叫內部的API去做一些事情,比如,自定義的高效能的NIO框架需要顯示的釋放DirectBuffer,使用反射繞開限制是一種常見的辦法。

那麼具體怎麼實現呢?在後面的程式碼示例中會有說明。

3、Modifier

反射包中還有一個重要的類Modifier,該類是靜態類,其中的方法也是靜態方法。
Modifier.Class類中getModifiers()函式返回一個用於描述類,構造器,方法和域的修飾符的整形數值。、
呼叫Modifier.toString()

方法將整型數值轉變成字串,也是就我們熟悉的public,private,static,final等修飾符。

三、反射包的詳解與程式碼實踐

package com.newframe.controllers.api;

import com.newframe.entity.test.TestUser;

import java.lang.reflect.*;

/**
 * @author:wangdong
 * @description:反射
 */

/**
 * 建立一個user類
 * 可不在與main方法同一個類中建立
 */
class User{

    //定一個成員變數:姓名
    private String name;

    //無參構造
    public User(){}
    //有參構造
    public User(String name) {
        this.name = name;
    }
    //get方法
    public String getName() {
        return name;
    }
    //set方法
    public void setName(String name) {
        this.name = name;
    }
}

/**
 * 測試Class類的函式和反射庫中的函式
 */
public class TestReflectController {

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {

        //獲得User類的Class物件
        Class<?> cc = User.class;

        //下面這個是通過new出來的物件,並通過set給成員變數賦值
        User user1 = new User();
        user1.setName("hello");
        //下面這個是通過構造器的方式

        //1.有參構造器
        //先得到有參構造器的資訊,再根據構造器的資訊,由newInstance()函式建立一個User物件
        Constructor<?> constructor = cc.getConstructor(String.class);
        //這個constructor.newInstance方法就是反射包下java.lang.reflect.Constructor類下的newInstance方法
        User user2 = (User) constructor.newInstance("world");
        System.out.println(user1.getName());//hello
        System.out.println(user2.getName());//world

        //2.無參構造器
        Constructor<?> constructor1 = cc.getConstructor();
        //無參構造不能夠像1一樣賦值
        //User user3 = (User) constructor1.newInstance("熊本");
        User user3 = (User) constructor1.newInstance();
        user3.setName("熊本");
        System.out.println(user3.getName());//熊本

        //由無參構造器建立物件時,可不必獲得構造器,直接由Class物件呼叫newInstance()方法。
        Class<?> cc2 = User.class;
        User user4 = (User) cc2.newInstance();
        user4.setName("同學");
        System.out.println(user4.getName());//同學

        //3.下面2個輸出語句可看出cc儲存類資訊,輸出的是“class + 類名”。cc.newInstance()是具體類的物件。
        System.out.println(cc);//class com.newframe.controllers.api.User
        System.out.println(cc.newInstance());//[email protected]


        //4.AccessibleObject、Field類
        //首先得到有參建構函式的資訊,然後根據建構函式例項化一個物件。
        //由getDeclaredField()函式得到類裡面的私有成員變數,訪問私有成員變數要用setAccessible()函式設定訪問許可權。
        //Field類物件得到成員變數後還可以設定該變數的值,使用set()方法。
        Constructor<?> constructor2 = cc.getConstructor(String.class);
        User user5 = (User) constructor2.newInstance("改變前:100");
        Field field = cc.getDeclaredField("name");
        field.setAccessible(true);
        field.set(user5,"改變後:50");
        System.out.println(user5.getName());//改變後:50

        //5.Method.invoke
        //首先根據獲得的建構函式資訊例項化一個物件
        //然後由函式名獲得類中的公有函式,getMethod("函式名")
        //invoke()方法執行由getMethod()獲得的函式,這裡獲得的函式是getter()
        //對於獲得的有參函式,invoke(物件)裡只新增物件名。
        Constructor<?> constructor3 = cc.getConstructor(String.class);
        User user6 = (User) constructor3.newInstance("你好世界");
        //getName是User實體類中的方法
        System.out.println(cc.getMethod("getName").invoke(user6));//你好世界

        //對於獲得到的無參函式,在呼叫getMethod()函式時,要在getMethod()中指定被獲得函式的"函式名"和"引數型別"
        //並且在執行該函式(即呼叫invoke()函式時),要指定物件和引數型別的具體例項。
        User user7 = (User) cc.newInstance();
        Method method = cc.getMethod("setName", String.class);
        method.invoke(user7,"你好哇,這個世界");
        System.out.println(user7.getName());//你好哇,這個世界

        //6.Modifier類
        //Class類中getModifiers()函式返回一個用於描述類,構造器,方法和域的修飾符的整形數值。、
        //呼叫Modifier.toString()方法將整型數值轉變成字串,也是就我們熟悉的public,private,static,final等修飾符。
        System.out.println(Modifier.toString(cc.getModifiers()));//無
        System.out.println(Modifier.toString(constructor.getModifiers()));//public
        System.out.println(Modifier.toString(field.getModifiers()));//private
        System.out.println(Modifier.toString(method.getModifiers()));//public

        //同時,Modifier類還有一些判斷修飾符是不是某一型別的方法。
        System.out.println(Modifier.isPublic(cc.getModifiers()));
        System.out.println(Modifier.isPublic(constructor.getModifiers()));

    }
}

四、通過反射呼叫其他類的方法

下面以在另一個類的main中呼叫其他類的main方法。

首先要有一個被呼叫的類:TestPrint
package com.newframe.controllers.api;

/**
 * @author:wangdong
 * @description:反射測試的列印類
 */
public class TestPrint {

    public static void main(String[] args) {
        for (String arg: args) {
            System.out.println(arg);
        }
    }
}
需要有一個呼叫類去呼叫TestPrint類中的主方法
package com.newframe.controllers.api;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author:wangdong
 * @description:測試反射呼叫其他類的方法
 */
public class TestReflectInvoke {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //傳統的方式呼叫
        TestPrint.main(new String[]{"hello","world"});//輸出hello world

        //反射呼叫,路徑為TestPrint的包路徑
        Class<?> cc = Class.forName("com.newframe.controllers.api.TestPrint");
        Method method = cc.getMethod("main", String[].class);
        //這裡需要用Object強轉,例項化字串陣列物件時要在前面加(Object),不然會報引數個數不匹配的錯誤。
        method.invoke(null,(Object) new String[]{"你好","世界"});
    }
}

五、陣列的反射

因為Array陣列屬於反射包java.lang.reflect.Array;

package com.newframe.controllers.api;

import java.lang.reflect.Array;

/**
 * @author:wangdong
 * @description:測試陣列的反射
 */
public class TestReflectArray {

    public static void main(String[] args) throws ClassNotFoundException {
        //新建4個不同的陣列
        //一維陣列
        int[] a = new int[3];
        int[] b = new int[4];
        //二維陣列
        int[][] c = new int[3][4];
        //String型別的陣列
        String[] d = new String[3];

        //可以對比各個陣列所屬的類是否相同
        System.out.println(a.getClass() == b.getClass());//true
        //System.out.println(a.getClass() == c.getClass());不同型別的陣列不同比較
        System.out.println(a.length == c.length);//但是可以比較長度,true
        System.out.println(a.getClass().getName());//[I
        System.out.println(d.getClass().getName());//[Ljava.lang.String;
        System.out.println(a.getClass().getSuperclass().getName());//java.lang.Object

        //利用反射生成一個數組
        int[] e = (int[]) Array.newInstance(int.class,3);
        //0為陣列索引下標,1為數字值
        Array.set(e,0,1);
        Array.set(e,1,2);
        Array.set(e,2,3);
        System.out.println(Array.get(e,0));
        System.out.println(Array.get(e, 1));
        System.out.println(Array.get(e, 2));

        //利用反射獲取陣列型別常用的一些方式
        Class<?> cc = String[].class;
        Class<?> cc1 = Class.forName("[I");
        Class<?> cc2 = Class.forName("[Ljava.lang.String;");

        System.out.println(cc);
        System.out.println(cc1);
        System.out.println(cc2);

        //通過已有的物件獲取陣列的型別
        Class<?> cc3 = e.getClass();
        System.out.println(cc3);
        //根據型別資訊獲取陣列內成員的型別
        Class<?> cc4 = cc3.getComponentType();
        System.out.println(cc4);//int
    }
}

六、友情索引

在知乎上有關於反射的討論,可以參考一下:知乎反射

做技術,最重要的還是動手啊,很多概念,類似於反射啊、動態代理啊。
你不動手,你光看,肯定是不會深入理解的。
你動手敲一邊程式碼,看看原始碼,立刻理解完全就不一樣了啊。