關於java反射及相關使用的部分理解

一、類的加載時機

當程序要使用某個類時,如果該類還未被加載到內存中,系統會通過加載,連接,初始化三步來實現對這個類進行初始化:
(1)加載:
就是指將class文件讀入內存,併爲之創建一個Class對象。任何類被使用時系統都會建立一個Class對象。
(2)連接:
驗證是否有正確的內部結構,並和其他類協調一致,準備 負責爲類的靜態成員分配內存,並設置默認初始化值
(3)初始化:
初始化成員變量等等

類的加載時機分爲以下幾種:
(1)創建類的實例
(2)訪問類的靜態變量,或者爲靜態變量賦值
(3)調用類的靜態方法
(4)初始化某個類的子類
(5)使用反射方式來強制創建某個類或接口對應的java.lang.Class對象

二、什麼是反射

衆所周知,創建一個對象分爲三個階段:
1.源文件階段 .java的文件
2.字節碼階段 .class
3.創建對象階段 new 對象名稱

java的反射機制就是指:
在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;
對於任意一個對象,都能夠調用它的任意一個方法和屬性;
這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。想要使用反射,就必須得要獲取字節碼文件

用途
在日常的第三方應用開發過程中,經常會遇到某個類的某個成員變量、方法或是屬性是私有的或是隻對系統應用開放,這時候就可以利用Java的反射機制通過反射來獲取所需的私有成員或是方法。

接下來通過代碼來進行演示,首先我們創建一個Person類:

package com.zhangyi.demo;

/**
 * @author zhangyi
 */
public class Person {
    public String name;
    public Integer age;
    private String food;

    //無參的構造方法
    public Person() {

    }

    //有參的構造方法
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }


    public void show() {
        System.out.println("我是" + this.name + ",今年" + this.age + "歲,我今天吃了"+this.food);
    }
    private void eat(String food){
		System.out.println("我今天吃了"+ food);
    }
}

之前提到,想要使用反射,就必須得要獲取字節碼文件,獲取類的字節碼文件有以下三種方式,以Person類爲例,通過Junit4單元測試來進行演示:

public class PersonTest {

    @Test
    public void test1() throws ClassNotFoundException {
        /*獲取類的字節碼的三種方式*/
        
       /* 第一種  Class類中靜態方法forName()  一般用於讀取配置文件*/
        Class<?> clazz1 = Class.forName("com.zhangyi.demo.Person");

        /*  第二種靜態屬性class   一般用於當作靜態方法的鎖對象*/
        Class<?> clazz2 = Person.class;

        /*第三種 Object類的getClass()方法  一般用於判斷兩個對象是否是同一個字節碼文件*/
        Person person = new Person();
        Class<?> clazz3 = person.getClass();

        /*判斷三者是否相等 */
        System.out.println(clazz1 == clazz2);
        System.out.println(clazz2 == clazz3);
        System.out.println(clazz1 == clazz3);
    }
}

輸出結果:


true
true
true

因此,我們可以看到,通過這三種方式創建Person類的字節碼文件都是一樣的。

三、反射的部分用法

(一)通過字節碼創建對象

1.通過無參的構造器創建對象:

    @Test
    public void test2() throws Exception {
        /* 1.獲取字節碼*/
        Class<?> clazz = Class.forName("com.zhangyi.demo.Person");
        /*2.調用字節碼的newInstance()方法*/
        Person person = (Person) clazz.newInstance();
        person.name = "張儀";
        person.age = 21;
        //food爲私有的變量,沒提供get、set方法,不可直接調用,後續會通過反射機制進行暴力賦值
        person.show();
    }

輸出結果:

我是張儀,今年21,今天吃了null

** 2.通過有參的構造器創建對象**

 @Test
    public void test3() throws Exception {
        /* 1.獲取字節碼的構造器 clazz.getConstructor(type.class) 因爲在反射階段操作的都是字節碼,不知道具體的類型,只有在創建對象的時候纔去給實際參數*/
        Class<?> clazz = Class.forName("com.zhangyi.demo.Person");
        Constructor constructor = clazz.getConstructor(String.class, Integer.class);
        /* 2.通過構造器創建對象 調用構造器的newInstance方法並傳入參數*/
        Person person = (Person) constructor.newInstance("張儀",21);
        person.show();
    }

輸出結果:

我是張儀,今年21,今天吃了null

(二)獲取字段

1.獲取公共的字段:

    @Test
    public void test4() throws Exception{
        /*1.根據反射創建對象*/
        Class<?> clazz = Class.forName("com.zhangyi.demo.Person");
        Person person = (Person) clazz.newInstance();
        /*2.獲取公共的字段*/
        Field name = clazz.getField("name");
        Field age = clazz.getField("age");
        /*3.設置person對象的屬性*/
        name.set(person,"張儀");
        age.set(person,21);
        person.show();
    }

輸出結果:

我是張儀,今年21,今天吃了null

2.獲取私有的字段

    @Test
    public void test5() throws Exception{
        /*1.根據反射創建對象*/
        Class<?> clazz = Class.forName("com.zhangyi.demo.Person");
        Person person = (Person) clazz.newInstance();
        /*2.獲取公共的字段 並且設置person對象的相應屬性*/
        Field name = clazz.getField("name");
        Field age = clazz.getField("age");
        name.set(person,"張儀");
        age.set(person,21);
        /*3.通過暴力反射獲取私有的字段 並且通過setAccessible方法去除私有權限 */
        Field food = clazz.getDeclaredField("food");
        food.setAccessible(true);
        //賦值
        food.set(person,"麪條");
        person.show();
    }

輸出結果:

我是張儀,今年21,今天吃了麪條

(三)獲取方法

1.獲取公共的方法:

    @Test
    public void test6() throws Exception{
        /*1.根據反射創建對象*/
        Class<?> clazz = Class.forName("com.zhangyi.demo.Person");
        Person person = (Person) clazz.newInstance();
        /*2.獲取公共的方法*/
        Method show = clazz.getMethod("show");

        /*3.調用invoke方法 若無參數則可以不用傳值*/
        show.invoke(person);
    }

輸出結果:

我是null,今年null歲,今天吃了null

2.獲取私有的方法:

  @Test
    public void test7() throws Exception{
        /*1.根據反射創建對象*/
        Class<?> clazz = Class.forName("com.zhangyi.demo.Person");
        Person person = (Person) clazz.newInstance();
        /*2.通過暴力反射獲取私有的方法 並且通過setAccessible方法去除私有權限  若有參數記得傳參*/
        Method eat = clazz.getDeclaredMethod("eat",String.class);
        eat.setAccessible(true);
        /*3.調用invoke方法 若無參數則可以不用傳值,有則記得傳值*/
        eat.invoke(person,"麪條");
    }

輸出結果:

我今天吃了麪條

(四)經典小案例

需求:如何繞過數組泛型檢測

科普一個小知識點:java的泛型又被稱之爲假泛型,java的泛型僅在編譯期有效,在運行期則會被擦除,即編譯的.class文件中並沒有泛型,也就是說所有的泛型參數類型在編譯後都會被清除掉。這就是所謂的類型擦除。感興趣可以自行編譯一下查看class文件。

需求實現代碼如下:

@Test
    public void  test8() throws Exception {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
       // list.add("添加字符串會報錯");

        /*獲取ArrayList的字節碼*/
        Class<?> clazz = Class.forName("java.util.ArrayList");
        /*獲取add方法*/
        Method add = clazz.getMethod("add", Object.class);
        /*此時調用方法add 給list添加字符串*/
        add.invoke(list,"此時添加字符串不會報錯");
        System.out.println(list);
    }

輸出結果:

[1, 此時添加字符串不會報錯]

以上就是我的部分見解。

發佈了8 篇原創文章 · 獲贊 0 · 訪問量 2316
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章