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

全部開發(fā)者教程

Java 反射

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

1. 什么是反射

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

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

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

圖中解釋了兩個(gè)問題:

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

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

2. 反射的使用場(chǎng)景

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

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

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

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

3. 反射常用類概述

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

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

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

例如,我們自定義了一個(gè) 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 會(huì)被編譯器編譯成字節(jié)碼文件 ImoocStudent.class,當(dāng) Java 虛擬機(jī)加載這個(gè) ImoocStudent.class 的時(shí)候,就會(huì)創(chuàng)建一個(gè) Class 類型的實(shí)例對(duì)象:

Class cls = new Class(ImoocStudent);

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

4. Class 類

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

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

4.2 獲取 Class 對(duì)象的方法

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

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

4.3 實(shí)例

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:對(duì)象.getClass()
        ImoocStudent student = new ImoocStudent();
        Class cls2 = student.getClass();

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

}

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

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

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

那么如何獲得 Constructor 對(duì)象?Class 提供了以下幾個(gè)方法來獲取:

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

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

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

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

實(shí)例演示
預(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);
    }
}
運(yùn)行案例 點(diǎn)擊 "運(yùn)行案例" 可查看在線運(yùn)行效果

運(yùn)行結(jié)果:

Hello Imooc

5. 訪問字段

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

5.1 獲取字段

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

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

獲取字段的實(shí)例如下:

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 實(shí)例
        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());
        }
    }

}

運(yùn)行結(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 個(gè)屬性,其中 position 為公有屬性,nicknamebalance 為私有屬性。我們通過類名.class 的方式獲取了 Class 實(shí)例,通過調(diào)用其實(shí)例方法并打印其返回結(jié)果,驗(yàn)證了獲取字段,獲取單個(gè)字段方法,在沒有找到該指定字段的情況下,會(huì)拋出一個(gè) NoSuchFieldException。

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

5.2 獲取字段值

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

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 {
        // 實(shí)例化一個(gè) ImoocStudent2 對(duì)象
        ImoocStudent2 imoocStudent2 = new ImoocStudent2("小慕", "架構(gòu)師");
        Class cls = imoocStudent2.getClass();
        Field position = cls.getField("position");
        Object o = position.get(imoocStudent2);
        System.out.println(o);
    }

}

運(yùn)行結(jié)果:

架構(gòu)師

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

這里值得注意的是,如果我們想要獲取 nickname 字段的值會(huì)稍有不同,因?yàn)樗撬接袑傩?,我們看?get() 方法會(huì)拋出 IllegalAccessException 異常,如果直接調(diào)用 get() 方法獲取私有屬性,就會(huì)拋出此異常。

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

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
    // 實(shí)例化一個(gè) ImoocStudent2 對(duì)象
    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);
}

此時(shí),就不會(huì)拋出異常,運(yùn)行結(jié)果:

小慕

5.2 為字段賦值

為字段賦值也很簡(jiǎn)單,調(diào)用 Field.set(Object obj, Object value) 方法即可,第一個(gè) Object 參數(shù)是指定的實(shí)例,第二個(gè) Object 參數(shù)是待修改的值。我們直接來看實(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 {
        // 實(shí)例化一個(gè) ImoocStudent3 對(duì)象
        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());
    }

}

運(yùn)行結(jié)果:

Colorful

6. 調(diào)用方法

Method 類代表某一個(gè)類中的一個(gè)成員方法。

6.1 獲取方法

Class 提供了以下幾個(gè)方法來獲取方法:

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

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

6.2 調(diào)用方法

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

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

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

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

實(shí)例演示
預(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 {
        // 實(shí)例化字符串對(duì)象
        String name = new String("Colorful");
        // 獲取 method 對(duì)象
        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);
    }
}
運(yùn)行案例 點(diǎn)擊 "運(yùn)行案例" 可查看在線運(yùn)行效果

運(yùn)行結(jié)果:

Color

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

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

7. 反射應(yīng)用

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

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

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

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

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

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

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

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

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

8. 小結(jié)

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