溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java中的反射機(jī)制有什么用

發(fā)布時間:2021-09-27 15:21:33 來源:億速云 閱讀:109 作者:小新 欄目:開發(fā)技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)Java中的反射機(jī)制有什么用,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

一、什么是反射?

(1)Java反射機(jī)制的核心是在程序運(yùn)行時動態(tài)加載類并獲取類的詳細(xì)信息,從而操作類或?qū)ο蟮膶傩院头椒ā1举|(zhì)是JVM得到class對象之后,再通過class對象進(jìn)行反編譯,從而獲取對象的各種信息。

(2)Java屬于先編譯再運(yùn)行的語言,程序中對象的類型在編譯期就確定下來了,而當(dāng)程序在運(yùn)行時可能需要動態(tài)加載某些類,這些類因為之前用不到,所以沒有被加載到JVM。通過反射,可以在運(yùn)行時動態(tài)地創(chuàng)建對象并調(diào)用其屬性,不需要提前在編譯期知道運(yùn)行的對象是誰。

二、為什么要用反射

首先,我們擁有一個接口 X 及其方法 test,和兩個對應(yīng)的實現(xiàn)類 A、B:

public class Test {
    interface X {
        public void test();
    }
    class A implements X{
        @Override
        public void test() {
             System.out.println("I am A");
        }
    }
    class B implements X{
        @Override
        public void test() {
            System.out.println("I am B");
    }
}

通常情況下,我們需要使用哪個實現(xiàn)類就直接 new 一個就好了,看下面這段代碼:

public class Test {    
    ......
    public static void main(String[] args) {
        X a = create1("A");
        a.test();
        X b = create1("B");
        b.test();
    }
    public static X create1(String name){
        if (name.equals("A")) {
            return new A();
        } else if(name.equals("B")){
            return new B();
        }
        return null;
    }
}

按照上面這種寫法,如果有成百上千個不同的 X 的實現(xiàn)類需要創(chuàng)建,那我們豈不是就需要寫上千個 if 語句來返回不同的 X 對象?

我們來看看看反射機(jī)制是如何做的:

public class Test {
    public static void main(String[] args) {
        X a = create2("A");
        a.test();
        X b = create2("B");
        b.testReflect();
    }
    // 使用反射機(jī)制
    public static X create2(String name){
        Class<?> class = Class.forName(name);
        X x = (X) class.newInstance();
        return x;
    }
}

向 create2() 方法傳入包名和類名,通過反射機(jī)制動態(tài)的加載指定的類,然后再實例化對象。

看完上面這個例子,相信諸位對反射有了一定的認(rèn)識。反射擁有以下四大功能:

  • 在運(yùn)行時(動態(tài)編譯)獲知任意一個對象所屬的類。

  • 在運(yùn)行時構(gòu)造任意一個類的對象。

  • 在運(yùn)行時獲知任意一個類所具有的成員變量和方法。

  • 在運(yùn)行時調(diào)用任意一個對象的方法和屬性。

上述這種動態(tài)獲取信息、動態(tài)調(diào)用對象的方法的功能稱為 Java 語言的反射機(jī)制。

三、Class類

要想理解反射,首先要理解 Class 類,因為 Class 類是反射實現(xiàn)的基礎(chǔ)。

Java中的反射機(jī)制有什么用

在程序運(yùn)行期間,JVM 始終為所有的對象維護(hù)一個被稱為運(yùn)行時的類型標(biāo)識,這個信息跟蹤著每個對象所屬的類的完整結(jié)構(gòu)信息,包括包名、類名、實現(xiàn)的接口、擁有的方法和字段等??梢酝ㄟ^專門的 Java 類訪問這些信息,這個類就是 Class 類。我們可以把 Class 類理解為類的類型,一個 Class 對象,稱為類的類型對象,一個 Class 對象對應(yīng)一個加載到 JVM 中的一個 .class 文件。

在通常情況下,一定是先有類再有對象。以下面這段代碼為例,類的正常加載過程是這樣的:

Java中的反射機(jī)制有什么用

import java.util.Date; // 先有類
public class Test {
    public static void main(String[] args) {
        Date date = new Date(); // 后有對象
        System.out.println(date);
    }
}

首先 JVM 會將你的代碼編譯成一個 .class 字節(jié)碼文件,然后被類加載器(Class Loader)加載進(jìn) JVM 的內(nèi)存中,同時會創(chuàng)建一個 Date 類的 Class 對象存到堆中(注意這個不是 new 出來的對象,而是類的類型對象)。JVM 在創(chuàng)建 Date 對象前,會先檢查其類是否加載,尋找類對應(yīng)的 Class 對象,若加載好,則為其分配內(nèi)存,然后再進(jìn)行初始化 new Date()。

需要注意的是,每個類只有一個 Class 對象,也就是說如果我們有第二條 new Date() 語句,JVM 不會再生成一個 Date 的 Class 對象,因為已經(jīng)存在一個了。這也使得我們可以利用 == 運(yùn)算符實現(xiàn)兩個類對象比較的操作:

System.out.println(date.getClass() == Date.getClass()); // true

那么在加載完一個類后,堆內(nèi)存的方法區(qū)就產(chǎn)生了一個 Class 對象,這個對象就包含了完整的類的結(jié)構(gòu)信息,我們可以通過這個 Class 對象看到類的結(jié)構(gòu),就好比一面鏡子。所以我們形象的稱之為:反射。

說的再詳細(xì)點,再解釋一下。上文說過,在通常情況下,一定是先有類再有對象,我們把這個通常情況稱為 “正”。那么反射中的這個 “反” 我們就可以理解為根據(jù)對象找到對象所屬的類(對象的出處)

Date date = new Date();
System.out.println(date.getClass()); // "class java.util.Date"

通過反射,也就是調(diào)用了 getClass() 方法后,我們就獲得了 Date 類對應(yīng)的 Class 對象,看到了 Date 類的結(jié)構(gòu),輸出了 Date 對象所屬的類的完整名稱,即找到了對象的出處。當(dāng)然,獲取 Class 對象的方式不止這一種。

Java中的反射機(jī)制有什么用

四、獲取 Class 類對象的四種方式

從 Class 類的源碼可以看出,它的構(gòu)造函數(shù)是私有的,也就是說只有 JVM 可以創(chuàng)建 Class 類的對象,我們不能像普通類一樣直接 new 一個 Class 對象。

Java中的反射機(jī)制有什么用

我們只能通過已有的類來得到一個 Class類對象,Java 提供了四種方式:

第一種:知道具體類的情況下可以使用:

Class alunbarClass = TargetObject.class;

但是我們一般是不知道具體類的,基本都是通過遍歷包下面的類來獲取 Class 對象,通過此方式獲取 Class 對象不會進(jìn)行初始化。

第二種:通過 Class.forName()傳入全類名獲取:

Class alunbarClass1 = Class.forName("com.xxx.TargetObject");

這個方法內(nèi)部實際調(diào)用的是 forName0:

Java中的反射機(jī)制有什么用

第 2 個 boolean 參數(shù)表示類是否需要初始化,默認(rèn)是需要初始化。一旦初始化,就會觸發(fā)目標(biāo)對象的 static 塊代碼執(zhí)行,static 參數(shù)也會被再次初始化。

第三種:通過對象實例 instance.getClass() 獲取:

Date date = new Date();
Class alunbarClass2 = date.getClass(); // 獲取該對象實例的 Class 類對象

第四種:通過類加載器 xxxClassLoader.loadClass() 傳入類路徑獲取

class clazz = ClassLoader.LoadClass("com.xxx.TargetObject");

通過類加載器獲取 Class 對象不會進(jìn)行初始化,意味著不進(jìn)行包括初始化等一些列步驟,靜態(tài)塊和靜態(tài)對象不會得到執(zhí)行。這里可以和 forName 做個對比。

五. 通過反射構(gòu)造一個類的實例

上面我們介紹了獲取 Class 類對象的方式,那么成功獲取之后,我們就需要構(gòu)造對應(yīng)類的實例。下面介紹三種方法,第一種最為常見,最后一種大家稍作了解即可。

① 使用 Class.newInstance

舉個例子:

Date date1 = new Date();
Class alunbarClass2 = date1.getClass();
Date date2 = alunbarClass2.newInstance(); // 創(chuàng)建一個與 alunbarClass2 具有相同類類型的實例

創(chuàng)建了一個與 alunbarClass2 具有相同類類型的實例。

需要注意的是,newInstance方法調(diào)用默認(rèn)的構(gòu)造函數(shù)(無參構(gòu)造函數(shù))初始化新創(chuàng)建的對象。如果這個類沒有默認(rèn)的構(gòu)造函數(shù), 就會拋出一個異常。

Java中的反射機(jī)制有什么用

② 通過反射先獲取構(gòu)造方法再調(diào)用

由于不是所有的類都有無參構(gòu)造函數(shù)又或者類構(gòu)造器是 private 的,在這樣的情況下,如果我們還想通過反射來實例化對象,Class.newInstance 是無法滿足的。

此時,我們可以使用 Constructor 的 newInstance 方法來實現(xiàn),先獲取構(gòu)造函數(shù),再執(zhí)行構(gòu)造函數(shù)。

Java中的反射機(jī)制有什么用

從上面代碼很容易看出,Constructor.newInstance 是可以攜帶參數(shù)的,而 Class.newInstance 是無參的,這也就是為什么它只能調(diào)用無參構(gòu)造函數(shù)的原因了。

大家不要把這兩個 newInstance 方法弄混了。如果被調(diào)用的類的構(gòu)造函數(shù)為默認(rèn)的構(gòu)造函數(shù),采用Class.newInstance() 是比較好的選擇, 一句代碼就 OK;如果需要調(diào)用類的帶參構(gòu)造函數(shù)、私有構(gòu)造函數(shù)等, 就需要采用 Constractor.newInstance()

Constructor.newInstance 是執(zhí)行構(gòu)造函數(shù)的方法。我們來看看獲取構(gòu)造函數(shù)可以通過哪些渠道,作用如其名,以下幾個方法都比較好記也容易理解,返回值都通過 Cnostructor 類型來接收。

批量獲取構(gòu)造函數(shù):

1)獲取所有"公有的"構(gòu)造方法

public Constructor[] getConstructors() { }

2)獲取所有的構(gòu)造方法(包括私有、受保護(hù)、默認(rèn)、公有)

public Constructor[] getDeclaredConstructors() { }
單個獲取構(gòu)造函數(shù):

1)獲取一個指定參數(shù)類型的"公有的"構(gòu)造方法

public Constructor getConstructor(Class... parameterTypes) { }

2)獲取一個指定參數(shù)類型的"構(gòu)造方法",可以是私有的,或受保護(hù)、默認(rèn)、公有

public Constructor getDeclaredConstructor(Class... parameterTypes) { }

舉個例子:

package fanshe;
public class Student {
    //(默認(rèn)的構(gòu)造方法)
    Student(String str){
        System.out.println("(默認(rèn))的構(gòu)造方法 s = " + str);
    }
    // 無參構(gòu)造方法
    public Student(){
        System.out.println("調(diào)用了公有、無參構(gòu)造方法執(zhí)行了。。。");
    }
    // 有一個參數(shù)的構(gòu)造方法
    public Student(char name){
        System.out.println("姓名:" + name);
    }
    // 有多個參數(shù)的構(gòu)造方法
    public Student(String name ,int age){
        System.out.println("姓名:"+name+"年齡:"+ age);//這的執(zhí)行效率有問題,以后解決。
    }
    // 受保護(hù)的構(gòu)造方法
    protected Student(boolean n){
        System.out.println("受保護(hù)的構(gòu)造方法 n = " + n);
    }
    // 私有構(gòu)造方法
    private Student(int age){
        System.out.println("私有的構(gòu)造方法年齡:"+ age);
    }
}
----------------------------------
public class Constructors {
    public static void main(String[] args) throws Exception {
        // 加載Class對象
        Class clazz = Class.forName("fanshe.Student");
        // 獲取所有公有構(gòu)造方法
        Constructor[] conArray = clazz.getConstructors();
        for(Constructor c : conArray){
            System.out.println(c);
        }
        // 獲取所有的構(gòu)造方法(包括:私有、受保護(hù)、默認(rèn)、公有)
        conArray = clazz.getDeclaredConstructors();
        for(Constructor c : conArray){
            System.out.println(c);
        }
        // 獲取公有、無參的構(gòu)造方法
        // 因為是無參的構(gòu)造方法所以類型是一個null,不寫也可以:這里需要的是一個參數(shù)的類型,切記是類型
        // 返回的是描述這個無參構(gòu)造函數(shù)的類對象。
        Constructor con = clazz.getConstructor(null);
        Object obj = con.newInstance(); // 調(diào)用構(gòu)造方法
        // 獲取私有構(gòu)造方法
        con = clazz.getDeclaredConstructor(int.class);
        System.out.println(con);
        con.setAccessible(true); // 為了調(diào)用 private 方法/域 我們需要取消安全檢查
        obj = con.newInstance(12); // 調(diào)用構(gòu)造方法
    }
}

③ 使用開源庫 Objenesis

Objenesis 是一個開源庫,和上述第二種方法一樣,可以調(diào)用任意的構(gòu)造函數(shù),不過封裝的比較簡潔:

public class Test {
    // 不存在無參構(gòu)造函數(shù)
    private int i;
    public Test(int i){
        this.i = i;
    }
    public void show(){
        System.out.println("test..." + i);
    }
}
------------------------
public static void main(String[] args) {
        Objenesis objenesis = new ObjenesisStd(true);
        Test test = objenesis.newInstance(Test.class);
        test.show();
    }

使用非常簡單,Objenesis 由子類 ObjenesisObjenesisStd實現(xiàn)。詳細(xì)源碼此處就不深究了,了解即可。

六. 通過反射獲取成員變量并使用

和獲取構(gòu)造函數(shù)差不多,獲取成員變量也分批量獲取和單個獲取。返回值通過 Field 類型來接收。

批量獲取:

1)獲取所有公有的字段

public Field[] getFields() { }

2)獲取所有的字段(包括私有、受保護(hù)、默認(rèn)的)

public Field[] getDeclaredFields() { }

單個獲取:

1)獲取一個指定名稱的公有的字段

public Field getField(String name) { }

2)獲取一個指定名稱的字段,可以是私有、受保護(hù)、默認(rèn)的

public Field getDeclaredField(String name) { }

獲取到成員變量之后,如何修改它們的值呢?

Java中的反射機(jī)制有什么用

set 方法包含兩個參數(shù):

  • obj:哪個對象要修改這個成員變量

  • value:要修改成哪個值

舉個例子:

package fanshe.field;
public class Student {
    public Student(){
    }
    public String name;
    protected int age;
    char sex;
    private String phoneNum;
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + ", sex=" + sex
                + ", phoneNum=" + phoneNum + "]";
    }
}
----------------------------------
public class Fields {
    public static void main(String[] args) throws Exception {
        // 獲取 Class 對象
        Class stuClass = Class.forName("fanshe.field.Student");
        // 獲取公有的無參構(gòu)造函數(shù)
        Constructor con = stuClass.getConstructor();
        // 獲取私有構(gòu)造方法
        con = clazz.getDeclaredConstructor(int.class);
        System.out.println(con);
        con.setAccessible(true); // 為了調(diào)用 private 方法/域 我們需要取消安全檢查
        obj = con.newInstance(12); // 調(diào)用構(gòu)造方法
        // 獲取所有公有的字段
        Field[] fieldArray = stuClass.getFields();
        for(Field f : fieldArray){
            System.out.println(f);
        }
         // 獲取所有的字段 (包括私有、受保護(hù)、默認(rèn)的)
        fieldArray = stuClass.getDeclaredFields();
        for(Field f : fieldArray){
            System.out.println(f);
        }
        // 獲取指定名稱的公有字段
        Field f = stuClass.getField("name");
        Object obj = con.newInstance(); // 調(diào)用構(gòu)造函數(shù),創(chuàng)建該類的實例
        f.set(obj, "劉德華"); // 為 Student 對象中的 name 屬性賦值
        // 獲取私有字段
        f = stuClass.getDeclaredField("phoneNum");
        f.setAccessible(true); // 暴力反射,解除私有限定
        f.set(obj, "18888889999"); // 為 Student 對象中的 phoneNum 屬性賦值
    }
}

七. 通過反射獲取成員方法并調(diào)用

同樣的,獲取成員方法也分批量獲取和單個獲取。返回值通過 Method 類型來接收。

批量獲?。?/h4>

1)獲取所有"公有方法"(包含父類的方法,當(dāng)然也包含 Object 類)

public Method[] getMethods() { }

2)獲取所有的成員方法,包括私有的(不包括繼承的)

public Method[] getDeclaredMethods() { }

單個獲?。?/h4>

獲取一個指定方法名和參數(shù)類型的成員方法:

public Method getMethod(String name, Class<?>... parameterTypes)

獲取到方法之后該怎么調(diào)用它們呢?

Java中的反射機(jī)制有什么用

invoke 方法中包含兩個參數(shù):

  • obj:哪個對象要來調(diào)用這個方法

  • args:調(diào)用方法時所傳遞的實參

舉個例子:

package fanshe.method;
public class Student {
    public void show1(String s){
        System.out.println("調(diào)用了:公有的,String參數(shù)的show1(): s = " + s);
    }
    protected void show2(){
        System.out.println("調(diào)用了:受保護(hù)的,無參的show2()");
    }
    void show3(){
        System.out.println("調(diào)用了:默認(rèn)的,無參的show3()");
    }
    private String show4(int age){
        System.out.println("調(diào)用了,私有的,并且有返回值的,int參數(shù)的show4(): age = " + age);
        return "abcd";
    }
}
-------------------------------------------
public class MethodClass {
    public static void main(String[] args) throws Exception {
        // 獲取 Class對象
        Class stuClass = Class.forName("fanshe.method.Student");
        // 獲取公有的無參構(gòu)造函數(shù)
        Constructor con = stuClass.getConstructor();
        // 獲取所有公有方法
        stuClass.getMethods();
        Method[] methodArray = stuClass.getMethods();
        for(Method m : methodArray){
            System.out.println(m);
        }
        // 獲取所有的方法,包括私有的
        methodArray = stuClass.getDeclaredMethods();
        for(Method m : methodArray){
            System.out.println(m);
        }
        // 獲取公有的show1()方法
        Method m = stuClass.getMethod("show1", String.class);
        System.out.println(m);
        Object obj = con.newInstance(); // 調(diào)用構(gòu)造函數(shù),實例化一個 Student 對象
        m.invoke(obj, "小牛肉");
        // 獲取私有的show4()方法
        m = stuClass.getDeclaredMethod("show4", int.class);
        m.setAccessible(true); // 解除私有限定
        Object result = m.invoke(obj, 20);
        System.out.println("返回值:" + result);
    }
}

八. 反射機(jī)制優(yōu)缺點

優(yōu)點: 比較靈活,能夠在運(yùn)行時動態(tài)獲取類的實例。

缺點

1)性能瓶頸:反射相當(dāng)于一系列解釋操作,通知 JVM 要做的事情,性能比直接的 Java 代碼要慢很多。

2)安全問題:反射機(jī)制破壞了封裝性,因為通過反射可以獲取并調(diào)用類的私有方法和字段。

九. 反射的經(jīng)典應(yīng)用場景

反射在我們實際編程中其實并不會直接大量的使用,但是實際上有很多設(shè)計都與反射機(jī)制有關(guān),比如:

動態(tài)代理機(jī)制使用 JDBC 連接數(shù)據(jù)庫Spring / Hibernate 框架(實際上是因為使用了動態(tài)代理,所以才和反射機(jī)制有關(guān))

為什么說動態(tài)代理使用了反射機(jī)制,下篇文章會給出詳細(xì)解釋。

JDBC 連接數(shù)據(jù)庫

在 JDBC 的操作中,如果要想進(jìn)行數(shù)據(jù)庫的連接,則必須按照以下幾步完成:

  • 通過 Class.forName() 加載數(shù)據(jù)庫的驅(qū)動程序 (通過反射加載)

  • 通過 DriverManager 類連接數(shù)據(jù)庫,參數(shù)包含數(shù)據(jù)庫的連接地址、用戶名、密碼

  • 通過 Connection 接口接收連接

  • 關(guān)閉連接

public static void main(String[] args) throws Exception {  
        Connection con = null; // 數(shù)據(jù)庫的連接對象  
        // 1\. 通過反射加載驅(qū)動程序
        Class.forName("com.mysql.jdbc.Driver"); 
        // 2\. 連接數(shù)據(jù)庫  
        con = DriverManager.getConnection(
            "jdbc:mysql://localhost:3306/test","root","root"); 
        // 3\. 關(guān)閉數(shù)據(jù)庫連接
        con.close(); 
}

Spring 框架

反射機(jī)制是 Java 框架設(shè)計的靈魂,框架的內(nèi)部都已經(jīng)封裝好了,我們自己基本用不著寫。典型的除了Hibernate 之外,還有 Spring 也用到了很多反射機(jī)制,最典型的就是 Spring 通過 xml 配置文件裝載 Bean(創(chuàng)建對象),也就是 Spring 的 IoC,過程如下:

  • 加載配置文件,獲取 Spring 容器

  • 使用反射機(jī)制,根據(jù)傳入的字符串獲得某個類的 Class 實例

// 獲取 Spring 的 IoC 容器,并根據(jù) id 獲取對象
public static void main(String[] args) {
    // 1.使用 ApplicationContext 接口加載配置文件,獲取 spring 容器
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
    // 2\. 使用反射機(jī)制,根據(jù)這個字符串獲得某個類的 Class 實例
    IAccountService aService = (IAccountService) ac.getBean("accountServiceImpl");
    System.out.println(aService);
}

關(guān)于“Java中的反射機(jī)制有什么用”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI