第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

全部開發(fā)者教程

Java 反射

本小節(jié)我們來學(xué)習(xí)一個 Java 語言中較為深入的概念 —— 反射(reflection),很多小伙伴即便參與了工作,可能也極少用到 Java 反射機制,但是如果你想要開發(fā)一個 web 框架,反射是不可或缺的知識點。本小節(jié)我們將了解到 什么是反射反射的使用場景,不得不提的 Class 類,如何通過反射訪問類內(nèi)部的字段、方法以及構(gòu)造方法等知識點。

1. 什么是反射

Java 的反射(reflection)機制是指在程序的運行狀態(tài)中,可以構(gòu)造任意一個類的對象,可以了解任意一個對象所屬的類,可以了解任意一個類的成員變量和方法,可以調(diào)用任意一個對象的屬性和方法。這種動態(tài)獲取程序信息以及動態(tài)調(diào)用對象的功能稱為 Java 語言的反射機制。反射被視為動態(tài)語言的關(guān)鍵。

通常情況下,我們想調(diào)用一個類內(nèi)部的屬性或方法,需要先實例化這個類,然后通過對象去調(diào)用類內(nèi)部的屬性和方法;通過 Java 的反射機制,我們就可以在程序的運行狀態(tài)中,動態(tài)獲取類的信息,注入類內(nèi)部的屬性和方法,完成對象的實例化等操作。

概念可能比較抽象,我們來看一下結(jié)合示意圖看一下:

圖中解釋了兩個問題:

  1. 程序運行狀態(tài)中指的是什么時刻Hello.java 源代碼文件經(jīng)過編譯得到 Hello.class 字節(jié)碼文件,想要運行這個程序,就要通過 JVM 的 ClassLoader (類加載器)加載 Hello.class,然后 JVM 來運行 Hello.class,程序的運行期間指的就是此刻;
  2. 什么是反射,它有哪些功能:在程序運行期間,可以動態(tài)獲得 Hello 類中的屬性和方法、動態(tài)完成 Hello 類的對象實例化等操作,這個功能就稱為反射。

說到這里,大家可能覺得,在編寫代碼時直接通過 new 的方式就可以實例化一個對象,訪問其屬性和方法,為什么偏偏要繞個彎子,通過反射機制來進行這些操作呢?下面我們就來看一下反射的使用場景。

2. 反射的使用場景

Java 的反射機制,主要用來編寫一些通用性較高的代碼或者編寫框架的時候使用。

通過反射的概念,我們可以知道,在程序的運行狀態(tài)中,對于任意一個類,通過反射都可以動態(tài)獲取其信息以及動態(tài)調(diào)用對象。

例如,很多框架都可以通過配置文件,來讓開發(fā)者指定使用不同的類,開發(fā)者只需要關(guān)心配置,不需要關(guān)心代碼的具體實現(xiàn),具體實現(xiàn)都在框架的內(nèi)部,通過反射就可以動態(tài)生成類的對象,調(diào)用這個類下面的一些方法。

下面的內(nèi)容,我們將學(xué)習(xí)反射的相關(guān) API,在本小節(jié)的最后,我將分享一個自己實際開發(fā)中的反射案例。

3. 反射常用類概述

學(xué)習(xí)反射就需要了解反射相關(guān)的一些類,下面我們來看一下如下這幾個類:

  • ClassClass 類的實例表示正在運行的 Java 應(yīng)用程序中的類和接口;
  • Constructor:關(guān)于類的單個構(gòu)造方法的信息以及對它的權(quán)限訪問;
  • Field:Field 提供有關(guān)類或接口的單個字段的信息,以及對它的動態(tài)訪問權(quán)限;
  • Method:Method 提供關(guān)于類或接口上單獨某個方法的信息。

字節(jié)碼文件想要運行都是要被虛擬機加載的,每加載一種類,Java 虛擬機都會為其創(chuàng)建一個 Class 類型的實例,并關(guān)聯(lián)起來。

例如,我們自定義了一個 ImoocStudent.java 類,類中包含有構(gòu)造方法、成員屬性、成員方法等:

public class ImoocStudent {
    // 無參構(gòu)造方法
    public ImoocStudent() {
    }

    // 有參構(gòu)造方法
    public ImoocStudent(String nickname) {
        this.nickname = nickname;
    }

    // 昵稱
    private String nickname;
    

    // 定義getter和setter方法
    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }
}

源碼文件 ImoocStudent.java 會被編譯器編譯成字節(jié)碼文件 ImoocStudent.class,當(dāng) Java 虛擬機加載這個 ImoocStudent.class 的時候,就會創(chuàng)建一個 Class 類型的實例對象:

Class cls = new Class(ImoocStudent);

JVM 為我們自動創(chuàng)建了這個類的對象實例,因此就可以獲取類內(nèi)部的構(gòu)造方法、屬性和方法等 ImoocStudent 的構(gòu)造方法就稱為 Constructor,可以創(chuàng)建對象的實例,屬性就稱為 Field,可以為屬性賦值,方法就稱為 Method,可以執(zhí)行方法。

4. Class 類

4.1 Class 類和 class 文件的關(guān)系

java.lang.Class 類用于表示一個類的字節(jié)碼(.class)文件。

4.2 獲取 Class 對象的方法

想要使用反射,就要獲取某個 class 文件對應(yīng)的 Class 對象,我們有 3 種方法:

  1. 類名.class:即通過一個 Class 的靜態(tài)變量 class 獲取,實例如下:
Class cls = ImoocStudent.class;
  1. 對象.getClass ():前提是有該類的對象實例,該方法由 java.lang.Object 類提供,實例如下:
ImoocStudent imoocStudent = new ImoocStudent("小慕");
Class imoocStudent.getClass();
  1. Class.forName (“包名。類名”):如果知道一個類的完整包名,可以通過 Class 類的靜態(tài)方法 forName() 獲得 Class 對象,實例如下:
class cls = Class.forName("java.util.ArrayList");

4.3 實例

package com.imooc.reflect;

public class ImoocStudent {
    // 無參構(gòu)造方法
    public ImoocStudent() {
    }

    // 有參構(gòu)造方法
    public ImoocStudent(String nickname) {
        this.nickname = nickname;
    }

    // 昵稱
    private String nickname;


    // 定義getter和setter方法
    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public static void main(String[] args) throws ClassNotFoundException {
        // 方法1:類名.class
        Class cls1 = ImoocStudent.class;

        // 方法2:對象.getClass()
        ImoocStudent student = new ImoocStudent();
        Class cls2 = student.getClass();

        // 方法3:Class.forName("包名.類名")
        Class cls3 = Class.forName("com.imooc.reflect.ImoocStudent");
    }

}

代碼中,我們在 com.imooc.reflect 包下定義了一個 ImoocStudent 類,并在主方法中,使用了 3 種方法獲取 Class 的實例對象,其 forName() 方法會拋出一個 ClassNotFoundException。

4.4 調(diào)用構(gòu)造方法

獲取了 Class 的實例對象,我們就可以獲取 Contructor 對象,調(diào)用其構(gòu)造方法了。

那么如何獲得 Constructor 對象?Class 提供了以下幾個方法來獲?。?/p>

  • Constructor getConstructor(Class...):獲取某個 public 的構(gòu)造方法;
  • Constructor getDeclaredConstructor(Class...):獲取某個構(gòu)造方法;
  • Constructor[] getConstructors():獲取所有 public 的構(gòu)造方法;
  • Constructor[] getDeclaredConstructors():獲取所有構(gòu)造方法。

通常我們調(diào)用類的構(gòu)造方法,這樣寫的(以 StringBuilder 為例):

// 實例化StringBuilder對象
StringBuilder name = new StringBuilder("Hello Imooc");

通過反射,要先獲取 Constructor 對象,再調(diào)用 Class.newInstance() 方法:

實例演示
預(yù)覽 復(fù)制
復(fù)制成功!
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectionDemo {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        // 獲取構(gòu)造方法
        Constructor constructor = StringBuffer.class.getConstructor(String.class);
        // 調(diào)用構(gòu)造方法
        Object str = constructor.newInstance("Hello Imooc");
        System.out.println(str);
    }
}
運行案例 點擊 "運行案例" 可查看在線運行效果

運行結(jié)果:

Hello Imooc

5. 訪問字段

前面我們知道了如何獲取 Class 實例,只要獲取了 Class 實例,就可以獲取它的所有信息。

5.1 獲取字段

Field 類代表某個類中的一個成員變量,并提供動態(tài)的訪問權(quán)限。Class 提供了以下幾個方法來獲取字段:

  • Field getField(name):根據(jù)屬性名獲取某個 public 的字段(包含父類繼承);
  • Field getDeclaredField(name):根據(jù)屬性名獲取當(dāng)前類的某個字段(不包含父類繼承);
  • Field[] getFields():獲得所有的 public 字段(包含父類繼承);
  • Field[] getDeclaredFields():獲取當(dāng)前類的所有字段(不包含父類繼承)。

獲取字段的實例如下:

package com.imooc.reflect;

import java.lang.reflect.Field;

public class ImoocStudent1 {

    // 昵稱 私有字段
    private String nickname;

    // 余額 私有字段
    private float balance;

    // 職位 公有字段
    public String position;

    public static void main(String[] args) throws NoSuchFieldException {
        // 類名.class 方式獲取 Class 實例
        Class cls1 = ImoocStudent1.class;
        // 獲取 public 的字段 position
        Field position = cls1.getField("position");
        System.out.println(position);

        // 獲取字段 balance
        Field balance = cls1.getDeclaredField("balance");
        System.out.println(balance);

        // 獲取所有字段
        Field[] declaredFields = cls1.getDeclaredFields();
        for (Field field: declaredFields) {
            System.out.print("name=" + field.getName());
            System.out.println("\ttype=" + field.getType());
        }
    }

}

運行結(jié)果:

public java.lang.String com.imooc.reflect.ImoocStudent1.position
private float com.imooc.reflect.ImoocStudent1.balance
name=nickname	type=class java.lang.String
name=balance	type=float
name=position	type=class java.lang.String

ImoocStudent1 類中含有 3 個屬性,其中 position 為公有屬性,nicknamebalance 為私有屬性。我們通過類名.class 的方式獲取了 Class 實例,通過調(diào)用其實例方法并打印其返回結(jié)果,驗證了獲取字段,獲取單個字段方法,在沒有找到該指定字段的情況下,會拋出一個 NoSuchFieldException。

調(diào)用獲取所有字段方法,返回的是一個 Field 類型的數(shù)組??梢哉{(diào)用 Field 類下的 getName() 方法來獲取字段名稱,getType() 方法來獲取字段類型。

5.2 獲取字段值

既然我們已經(jīng)獲取到了字段,那么就理所當(dāng)然地可以獲取字段的值??梢酝ㄟ^ Field 類下的 Object get(Object obj) 方法來獲取指定字段的值,方法的參數(shù) Object 為對象實例,實例如下:

package com.imooc.reflect;

import java.lang.reflect.Field;

public class ImoocStudent2 {

    public ImoocStudent2() {
    }

    public ImoocStudent2(String nickname, String position) {
        this.nickname = nickname;
        this.position = position;
    }

    // 昵稱 私有字段
    private String nickname;

    // 職位 公有屬性
    public String position;

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        // 實例化一個 ImoocStudent2 對象
        ImoocStudent2 imoocStudent2 = new ImoocStudent2("小慕", "架構(gòu)師");
        Class cls = imoocStudent2.getClass();
        Field position = cls.getField("position");
        Object o = position.get(imoocStudent2);
        System.out.println(o);
    }

}

運行結(jié)果:

架構(gòu)師

ImoocStudent2 內(nèi)部分別包含一個公有屬性 position 和一個私有屬性 nickname,我們首先實例化了一個 ImoocStudent2 對象,并且獲取了與其對應(yīng)的 Class 對象,然后調(diào)用 getField() 方法獲取了 position 字段,通過調(diào)用 Field 類下的實例方法 Object get(Object obj) 來獲取了 position 字段的值。

這里值得注意的是,如果我們想要獲取 nickname 字段的值會稍有不同,因為它是私有屬性,我們看到 get() 方法會拋出 IllegalAccessException 異常,如果直接調(diào)用 get() 方法獲取私有屬性,就會拋出此異常。

想要獲取私有屬性,必須調(diào)用 Field.setAccessible(boolean flag) 方法來設(shè)置該字段的訪問權(quán)限為 true,表示可以訪問。在 main() 方法中,獲取私有屬性 nickname 的值的實例如下:

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    // 實例化一個 ImoocStudent2 對象
    ImoocStudent2 imoocStudent2 = new ImoocStudent2("小慕", "架構(gòu)師");
    Class cls = imoocStudent2.getClass();
    Field nickname = cls.getDeclaredField("nickname");
    // 設(shè)置可以訪問
    nickname.setAccessible(true);
    Object o = nickname.get(imoocStudent2);
    System.out.println(o);
}

此時,就不會拋出異常,運行結(jié)果:

小慕

5.2 為字段賦值

為字段賦值也很簡單,調(diào)用 Field.set(Object obj, Object value) 方法即可,第一個 Object 參數(shù)是指定的實例,第二個 Object 參數(shù)是待修改的值。我們直接來看實例:

package com.imooc.reflect;

import java.lang.reflect.Field;

public class ImoocStudent3 {

    public ImoocStudent3() {
    }

    public ImoocStudent3(String nickname) {
        this.nickname = nickname;
    }

    // 昵稱 私有字段
    private String nickname;

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        // 實例化一個 ImoocStudent3 對象
        ImoocStudent3 imoocStudent3 = new ImoocStudent3("小慕");
        Class cls = imoocStudent3.getClass();
        Field nickname = cls.getDeclaredField("nickname");
        nickname.setAccessible(true);
        // 設(shè)置字段值
        nickname.set(imoocStudent3, "Colorful");
        // 打印設(shè)置后的內(nèi)容
        System.out.println(imoocStudent3.getNickname());
    }

}

運行結(jié)果:

Colorful

6. 調(diào)用方法

Method 類代表某一個類中的一個成員方法。

6.1 獲取方法

Class 提供了以下幾個方法來獲取方法:

  • Method getMethod(name, Class...):獲取某個 public 的方法(包含父類繼承);
  • Method getgetDeclaredMethod(name, Class...):獲取當(dāng)前類的某個方法(不包含父類);
  • Method[] getMethods():獲取所有 public 的方法(包含父類繼承);
  • Method[] getDeclareMethods():獲取當(dāng)前類的所有方法(不包含父類繼承)。

獲取方法和獲取字段大同小異,只需調(diào)用以上 API 即可,這里不再贅述。

6.2 調(diào)用方法

獲取方法的目的就是調(diào)用方法,調(diào)用方法也就是讓方法執(zhí)行。

通常情況下,我們是這樣調(diào)用對象下的實例方法(以 String 類的 replace() 方法為例):

String name = new String("Colorful");
String result = name.replace("ful", "");

改寫成通過反射方法調(diào)用:

實例演示
預(yù)覽 復(fù)制
復(fù)制成功!
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectionDemo1 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // 實例化字符串對象
        String name = new String("Colorful");
        // 獲取 method 對象
        Method method = String.class.getMethod("replace", CharSequence.class, CharSequence.class);
        // 調(diào)用 invoke() 執(zhí)行方法
        String result = (String) method.invoke(name,  "ful", "");
        System.out.println(result);
    }
}
運行案例 點擊 "運行案例" 可查看在線運行效果

運行結(jié)果:

Color

代碼中,調(diào)用 Method 實例的 invoke(Object obj, Object...args) 方法,就是通過反射來調(diào)用了該方法。

其中 invoke() 方法的第一個參數(shù)為對象實例,緊接著的可變參數(shù)就是要調(diào)用方法的參數(shù),參數(shù)要保持一致。

7. 反射應(yīng)用

Tips: 理解此部分內(nèi)容可能需要閱讀者有一定的開發(fā)經(jīng)驗

學(xué)習(xí)完了反射,大家可能依然非常疑惑,反射似乎離我們的實際開發(fā)非常遙遠(yuǎn),實際情況也的確是這樣的。因為我們在實際開發(fā)中基本不會用到反射。下面我來分享一個實際開發(fā)中應(yīng)用反射的案例。

場景是這樣的:有一個文件上傳系統(tǒng),文件上傳系統(tǒng)有多種不同的方式(上傳到服務(wù)器本地、上傳到七牛云、阿里云 OSS 等),因此就有多個不同的文件上傳實現(xiàn)類。系統(tǒng)希望通過配置文件來獲取用戶的配置,再去實例化對應(yīng)的實現(xiàn)類。因此,我們一開始的思路可能是這樣的(偽代碼):

public class UploaderFactory {
    
    // 通過配置文件獲取到的配置,可能為 local(上傳到本地) qiniuyun(上傳到七牛) 
    private String uploader;
    
    // 創(chuàng)建實現(xiàn)類對象的方法
    public Uploader createUploader() {
        switch (uploader) {
            case "local":
                // 實例化上傳到本地的實現(xiàn)類
                return new LocalUploader();
            case "qiniuyun":
                // 實例化上傳到七牛云的實現(xiàn)類
                return new QiniuUploader();
            default:
                break;
        }
        return null;
    }
}

createUploader() 就是創(chuàng)建實現(xiàn)類的方法,它通過 switch case 結(jié)構(gòu)來判斷從配置文件中獲取的 uploader 變量。

這看上去似乎沒有什么問題,但試想,后續(xù)我們的實現(xiàn)類越來越多,就需要一直向下添加 case 語句,并且要約定配置文件中的字符串要和 case 匹配才行。這樣的代碼既不穩(wěn)定也不健壯。

換一種思路考慮問題,我們可以通過反射機制來改寫這里的代碼。首先,約定配置文件的 uploader 配置項不再是字符串,改為類的全路徑命名。因此,在 createUploader() 方法中不再需要 switch case 結(jié)構(gòu)來判斷,直接通過 Class.forName(uploader) 就可以獲取 Class 實例,并調(diào)用其構(gòu)造方法實例化對應(yīng)的文件上傳對象,偽代碼如下:

public class UploaderFactory {
    
    // 通過配置文件獲取到的配置,實現(xiàn)類的包名.類名
    private String uploader;
    
    // 創(chuàng)建實現(xiàn)類對象的方法
    public Uploader createUploader() {
        // 獲取構(gòu)造方法
		Constructor constructor = Class.forName(uploader).getConstructor();
        return (Uploader) constructor.newInstance();
    }
}

通過反射實例化對應(yīng)的實現(xiàn)類,我們不需要再維護 UploaderFactory 下的代碼,其實現(xiàn)類的命名、放置位置也不受約束,只需要在配置文件中指定類名全路徑即可。

8. 小結(jié)

通過本小節(jié)的學(xué)習(xí),我們知道了反射是 Java 提供的一種機制,它可以在程序的運行狀態(tài)中,動態(tài)獲取類的信息,注入類內(nèi)部的屬性和方法,完成對象的實例化等操作。獲取 Class 對象有 3 種方法,通過學(xué)習(xí)反射的相關(guān)接口,我們了解到通過反射可以實現(xiàn)一切我們想要的操作。在本小節(jié)的最后,我也分享了一個我在實際開發(fā)中應(yīng)用反射的案例,希望能對大家有所啟發(fā)。