国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Java 中的反射機(jī)制(兩萬(wàn)字超全詳解)

這篇具有很好參考價(jià)值的文章主要介紹了Java 中的反射機(jī)制(兩萬(wàn)字超全詳解)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

一、反射概述

1. 什么是反射?

反射(Reflection),Java 中的反射機(jī)制是指,Java 程序在運(yùn)行期間可以獲取到一個(gè)對(duì)象的全部信息。

反射機(jī)制一般用來(lái)解決Java 程序運(yùn)行期間,對(duì)某個(gè)實(shí)例對(duì)象一無(wú)所知的情況下,如何調(diào)用該對(duì)象內(nèi)部的方法問(wèn)題。

2. 反射機(jī)制原理

反射機(jī)制允許 Java 程序在運(yùn)行時(shí)調(diào)用Reflection API取得任何類(lèi)的內(nèi)部信息(比如成員變量、構(gòu)造器、成員方法等),并能操作類(lèi)的實(shí)例對(duì)象的屬性以及方法。

在Java 程序中,JVM 加載完一個(gè)類(lèi)后,在堆內(nèi)存中就會(huì)產(chǎn)生該類(lèi)的一個(gè) Class 對(duì)象,一個(gè)類(lèi)在堆內(nèi)存中最多只會(huì)有一個(gè) Class 對(duì)象,這個(gè)Class 對(duì)象包含了該類(lèi)的完整結(jié)構(gòu)信息,我們通過(guò)這個(gè) Class 對(duì)象便可以得到該類(lèi)的完整結(jié)構(gòu)信息。

這個(gè) Class 對(duì)象就像是一面鏡子,我們透過(guò)這面鏡子可以清楚地看到類(lèi)的結(jié)構(gòu)信息。因此,我們形象的將獲取Class對(duì)象的過(guò)程稱(chēng)為:反射。如下圖:

Java 反射機(jī)制原理示意圖:

java反射,JavaSE,java,反射機(jī)制

3. 反射優(yōu)點(diǎn)和缺點(diǎn)

  1. 優(yōu)點(diǎn):可以動(dòng)態(tài)地創(chuàng)建和使用對(duì)象,反射機(jī)制是 Java 框架的底層核心,其使用靈活,沒(méi)有反射機(jī)制,底層框架就失去支撐。
  2. 缺點(diǎn):使用反射基本是解釋執(zhí)行,對(duì)程序執(zhí)行速度有影響。

4. 類(lèi)加載概述

在深入講解反射前,先來(lái)介紹一下 Java中類(lèi)的加載與反射機(jī)制。

反射機(jī)制是 Java實(shí)現(xiàn)動(dòng)態(tài)語(yǔ)言的關(guān)鍵,也就是通過(guò)反射實(shí)現(xiàn)類(lèi)的動(dòng)態(tài)加載。

  1. 靜態(tài)加載:編譯時(shí)就加載相關(guān)的類(lèi),如果程序中不存在該類(lèi)則編譯報(bào)錯(cuò),依賴(lài)性太強(qiáng)。

  2. 動(dòng)態(tài)加載:運(yùn)行時(shí)加載相關(guān)的類(lèi),即使程序中不存在該類(lèi),但如果運(yùn)行時(shí)未使用到該類(lèi),也不會(huì)編譯錯(cuò)誤,依賴(lài)性較弱。

舉個(gè)例子:

public class ClassLoad {
    public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
        int key = sc.nextInt();
        switch(key) {
            case 0:
                Cat cat = new Cat();
                break;
            case 1:
                // 通過(guò)反射創(chuàng)建一個(gè)Dog 類(lèi)對(duì)象,不提供代碼,只是文字說(shuō)明
                break;
        }
    }
}
  • 上面代碼中,根據(jù) key 的值選擇創(chuàng)建 Cat/Dog 對(duì)象,但是在代碼編譯時(shí),編譯器會(huì)先檢查程序中是否存在 Cat 類(lèi),如果沒(méi)有,則會(huì)編譯報(bào)錯(cuò);編譯器不會(huì)檢查是否存在 Dog 類(lèi),因?yàn)?Dog 類(lèi)是使用反射的方式創(chuàng)建的,所以即使程序中不存在 Dog 類(lèi),也不會(huì)編譯報(bào)錯(cuò),而是等到程序運(yùn)行時(shí),我們真正選擇了 key = 1 后,才會(huì)去檢查 Dog 類(lèi)是否存在。

類(lèi)加載的時(shí)機(jī):

  1. 靜態(tài)加載
  • 當(dāng)新創(chuàng)建一個(gè)對(duì)象時(shí)(new),該類(lèi)會(huì)被加載;
  • 當(dāng)調(diào)用類(lèi)中的靜態(tài)成員時(shí),該類(lèi)會(huì)被加載;
  • 當(dāng)子類(lèi)被加載時(shí),其超類(lèi)也會(huì)被加載;
  1. 動(dòng)態(tài)加載
  • 通過(guò)反射的方式,在程序運(yùn)行時(shí)使用到哪個(gè)類(lèi),該類(lèi)才會(huì)被加載;

類(lèi)加載的過(guò)程圖:

java反射,JavaSE,java,反射機(jī)制

5. 類(lèi)加載各階段完成的功能

  1. 加載階段:將類(lèi)的 class 文件讀入內(nèi)存,并為之創(chuàng)建一個(gè) java.lang.Class 對(duì)象,此過(guò)程由類(lèi)加載器完成。
  2. 連接階段:又分為驗(yàn)證、準(zhǔn)備、解析三個(gè)小階段,此階段會(huì)將類(lèi)的二進(jìn)制數(shù)據(jù)合并到 JRE 中。
  3. 初始化階段:JVM 負(fù)責(zé)對(duì)類(lèi)的靜態(tài)成員進(jìn)行初始化。

如下圖所示:

java反射,JavaSE,java,反射機(jī)制

5.1 加載階段

JVM 在該階段的主要目的是將字節(jié)碼從不同的數(shù)據(jù)源(可能是 class 文件、jar 包、甚至網(wǎng)絡(luò)文件)轉(zhuǎn)換為二進(jìn)制字節(jié)流加載到內(nèi)存中,并生成一個(gè)代表該類(lèi)的 java.lang.Class 對(duì)象。

5.2 連接階段——驗(yàn)證

java反射,JavaSE,java,反射機(jī)制

5.3 連接階段——準(zhǔn)備

JVM 會(huì)在該階段對(duì)靜態(tài)變量分配內(nèi)存并進(jìn)行默認(rèn)初始化(不同數(shù)據(jù)類(lèi)型會(huì)有其默認(rèn)初始值,如:int ---- 0,boolean ---- false 等)。這些變量的內(nèi)存空間會(huì)在方法區(qū)中分配。

舉例如下:

public class ClassLoad {
    public static void main(String[] args) {
		// 屬性=成員變量=字段
    	// 類(lèi)加載的連接階段-準(zhǔn)備,屬性是如何加載的

    	public int n1 = 10;
    	public static  int n2 = 20;
    	public static final  int n3 = 30;
    }
}
  • 代碼說(shuō)明:

    1. n1 是實(shí)例屬性, 不是靜態(tài)變量,因此在準(zhǔn)備階段,是不會(huì)分配內(nèi)存

    2. n2 是靜態(tài)變量,在該階段 JVM 會(huì)為其分配內(nèi)存,n2 默認(rèn)初始化的值為 0 ,而不是 20

    3. n3 被 static final 修飾,是常量, 它和靜態(tài)變量不一樣, 其一旦賦值后值就不變,因此其默認(rèn)初始化 n3 = 30

5.4 連接階段——解析

JVM 將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程。

5.5 初始化階段

  1. 在初始化階段,JVM 才會(huì)真正執(zhí)行類(lèi)中定義的 Java程序代碼,此階段是執(zhí)行<clinit>() 方法的過(guò)程。
  2. <clinit>() 方法是由編譯器按語(yǔ)句在源文件中出現(xiàn)的順序,依次自動(dòng)收集類(lèi)中的所有靜態(tài)變量的賦值操作和靜態(tài)代碼塊中的語(yǔ)句,并進(jìn)行合并的過(guò)程。
  3. JVM 會(huì)保證一個(gè)類(lèi)的 <clinit>() 方法 在多線程環(huán)境中被正確地加鎖、同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類(lèi),那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類(lèi)的 <clinit>() 方法,其他線程都要阻塞等待,直到活動(dòng)線程執(zhí)行 <clinit>() 方法完畢。

舉例如下:

public class ClassLoad {
    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println(B.num);// 直接使用類(lèi)的靜態(tài)屬性,也會(huì)導(dǎo)致類(lèi)的加載
    }
}

class B {
    static { // 靜態(tài)代碼塊
        System.out.println("B 靜態(tài)代碼塊被執(zhí)行");
        num = 300;
    }

    static int num = 100;// 靜態(tài)變量

    public B() {// 構(gòu)造器
        System.out.println("B() 構(gòu)造器被執(zhí)行");
    }
}

輸出如下:

B 靜態(tài)代碼塊被執(zhí)行
100

代碼說(shuō)明:

  1. 加載階段:加載 B類(lèi),并生成 B的 class對(duì)象
  2. 連接階段:進(jìn)行默認(rèn)初始化 num = 0
  3. 初始化階段:執(zhí)行 <clinit>() 方法,該方法會(huì)依次自動(dòng)收集類(lèi)中的所有靜態(tài)變量的賦值操作和靜態(tài)代碼塊中的語(yǔ)句,并合并。如下:
clinit() {
	System.out.println("B 靜態(tài)代碼塊被執(zhí)行");
    num = 300;
    num = 100;
}
  • 合并后: num = 100

注意:加載類(lèi)的時(shí)候,具有同步機(jī)制控制。如下:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
	//正因?yàn)橛羞@個(gè)機(jī)制,才能保證某個(gè)類(lèi)在內(nèi)存中, 只有一份Class對(duì)象
    synchronized (getClassLoadingLock(name)) {
    	//....
    }
}

二、Class 類(lèi)

  1. Class也是一個(gè)類(lèi),其類(lèi)名就叫Class,因此它也繼承 Object 類(lèi)
  2. Class類(lèi)對(duì)象不是由我們程序員創(chuàng)建(new)出來(lái)的,而是在類(lèi)加載時(shí)由 JVM 自動(dòng)創(chuàng)建的
  3. 堆內(nèi)存中最多只會(huì)存在某個(gè)類(lèi)的唯一的Class對(duì)象,因?yàn)轭?lèi)只會(huì)加載一次
  4. 每個(gè)類(lèi)的實(shí)例對(duì)象都會(huì)知道自己對(duì)應(yīng)的Class對(duì)象
  5. 通過(guò)Class類(lèi)對(duì)象可以完整地得到其對(duì)應(yīng)的類(lèi)的信息,通過(guò)一系列反射 API
  6. 類(lèi)的字節(jié)碼二進(jìn)制數(shù)據(jù),是存放在方法區(qū)的,又稱(chēng)為類(lèi)的元數(shù)據(jù)(包括方法代碼、變量名、方法名、訪問(wèn)權(quán)限等等)

除了int等基本類(lèi)型外,Java的其他類(lèi)型全部都是class(包括interface)。例如:

  • String
  • Object
  • Runnable
  • Exception

仔細(xì)思考,我們可以得出結(jié)論:類(lèi)class(包括接口interface)的本質(zhì)是數(shù)據(jù)類(lèi)型(Type)。無(wú)繼承關(guān)系的數(shù)據(jù)類(lèi)型無(wú)法賦值:

Number n = new Double(123.456); // 編譯成功
String s = new Double(123.456); // 編譯錯(cuò)誤

而類(lèi)class是由 JVM 在執(zhí)行過(guò)程中動(dòng)態(tài)加載的。JVM在第一次讀取到一種類(lèi)class時(shí),會(huì)將其加載進(jìn)內(nèi)存。

每加載一種class,JVM就為其創(chuàng)建一個(gè)Class類(lèi)的對(duì)象,并將兩者關(guān)聯(lián)起來(lái)。注意:這里的Class類(lèi)是一個(gè)名字叫Class的類(lèi)class。它長(zhǎng)這樣:

public final class Class {
    private Class() {}
}

String類(lèi)為例,當(dāng) JVM 加載String類(lèi)時(shí),它首先讀取String.class文件到內(nèi)存,然后,在堆中為String類(lèi)創(chuàng)建一個(gè)Class類(lèi)對(duì)象并將兩者關(guān)聯(lián)起來(lái):

Class cls = new Class(String);
  • 注意:這個(gè)Class類(lèi)對(duì)象是 JVM 內(nèi)部創(chuàng)建的,如果我們查看 JDK 源碼,可以發(fā)現(xiàn)Class類(lèi)的構(gòu)造方法是private,即只有 JVM 能創(chuàng)建Class類(lèi)對(duì)象,我們程序員自己的 Java 程序是無(wú)法創(chuàng)建Class類(lèi)對(duì)象的。

所以,JVM持有的每個(gè)Class類(lèi)對(duì)象都指向一個(gè)數(shù)據(jù)類(lèi)型(classinterface):

┌───────────────────────────┐
│      Class Instance       │──────> String
├───────────────────────────┤
│name = "java.lang.String"  │
└───────────────────────────┘
┌───────────────────────────┐
│      Class Instance       │──────> Random
├───────────────────────────┤
│name = "java.util.Random"  │
└───────────────────────────┘
┌───────────────────────────┐
│      Class Instance       │──────> Runnable
├───────────────────────────┤
│name = "java.lang.Runnable"│
└───────────────────────────┘

一個(gè)Class類(lèi)對(duì)象包含了其對(duì)應(yīng)的類(lèi)class的所有完整信息:

┌───────────────────────────┐
│      Class Instance       │──────> String
├───────────────────────────┤
│name = "java.lang.String"  │
├───────────────────────────┤
│package = "java.lang"      │
├───────────────────────────┤
│super = "java.lang.Object" │
├───────────────────────────┤
│interface = CharSequence...│
├───────────────────────────┤
│field = value[],hash,...   │
├───────────────────────────┤
│method = indexOf()...      │
└───────────────────────────┘

由于JVM為每個(gè)加載的類(lèi)class創(chuàng)建了對(duì)應(yīng)的Class類(lèi)對(duì)象,并在實(shí)例中保存了該類(lèi)class的所有信息,包括類(lèi)名、包名、父類(lèi)、實(shí)現(xiàn)的接口、所有方法、字段等,因此,如果獲取了某個(gè)Class類(lèi)對(duì)象,我們就可以通過(guò)這個(gè)Class類(lèi)對(duì)象獲取到其對(duì)應(yīng)的類(lèi)class的所有信息。

這種通過(guò)Class實(shí)例獲取類(lèi)class信息的方法稱(chēng)為反射(Reflection)。

如何獲取一個(gè)classClass實(shí)例?有5個(gè)方法:

方法一:直接通過(guò)一個(gè)類(lèi)class中的靜態(tài)變量class獲?。?/p>

Class cls = String.class;// class 是 String 類(lèi)中的一個(gè)靜態(tài)變量

方法二:如果我們有一個(gè)類(lèi)class的對(duì)象,可以通過(guò)該對(duì)象引用提供的getClass()方法獲?。?/p>

String s = "Hello";
Class cls = s.getClass();// 調(diào)用 String類(lèi)對(duì)象 s的 getClass() 方法獲取

方法三:如果知道一個(gè)類(lèi)class的完整類(lèi)名,可以通過(guò)Class類(lèi)的靜態(tài)方法Class.forName()獲?。?/p>

Class cls = Class.forName("java.lang.String");// java.lang.String 是 String 類(lèi)的完整類(lèi)名

方法四:對(duì)于基本數(shù)據(jù)類(lèi)型(int、char、boolean、float 等),通過(guò) 基本數(shù)據(jù)類(lèi)型.class 獲?。?/p>

Class integerClass = int.class;
Class characterClass = char.class;
Class booleanClass = boolean.class;
System.out.println(integerClass);// int

方法五:對(duì)于基本數(shù)據(jù)類(lèi)型對(duì)應(yīng)的包裝類(lèi),可以通過(guò)類(lèi)中的靜態(tài)變量TYPE獲取到Class類(lèi)對(duì)象:

Class type1 = Integer.TYPE;
Class type2 = Character.TYPE;
System.out.println(type1);// int
  • 注意:對(duì)于基本數(shù)據(jù)類(lèi)型獲取到的Class類(lèi)對(duì)象和基本數(shù)據(jù)類(lèi)型對(duì)應(yīng)的包裝類(lèi)獲取到的Class類(lèi)對(duì)象,是同一個(gè)Class類(lèi)對(duì)象:
System.out.println(integerClass.hashCode());
System.out.println(type1.hashCode());// 兩者相等,說(shuō)明都是指向 int

因?yàn)?code>Class類(lèi)對(duì)象在 JVM 中是唯一的,所以,上述方法獲取的Class類(lèi)對(duì)象是同一個(gè)對(duì)象。可以用==比較兩個(gè)Class類(lèi)對(duì)象:

Class cls1 = String.class;

String s = "Hello";
Class cls2 = s.getClass();

boolean sameClass = cls1 == cls2; // true

注意一下用==比較Class類(lèi)對(duì)象和用instanceof的差別:

Integer n = new Integer(123);

boolean b1 = n instanceof Integer; // true,因?yàn)?n是 Integer  類(lèi)型
boolean b2 = n instanceof Number; // true,因?yàn)?n 是 Number 類(lèi)型的子類(lèi)

boolean b3 = n.getClass() == Integer.class; // true,因?yàn)?n.getClass() 返回 Integer.class
boolean b4 = n.getClass() == Number.class; // false,因?yàn)?Integer.class != Number.class
  • instanceof不但匹配指定類(lèi)型,還匹配指定類(lèi)型的子類(lèi)。而用==比較class類(lèi)對(duì)象可以精確地判斷數(shù)據(jù)類(lèi)型,但不能用作子類(lèi)型比較。
    • 通常情況下,我們應(yīng)該用instanceof判斷數(shù)據(jù)類(lèi)型,因?yàn)槊嫦虺橄缶幊痰臅r(shí)候,我們不關(guān)心具體的子類(lèi)型。
    • 只有在需要精確判斷一個(gè)類(lèi)型是不是某個(gè)class的時(shí)候,我們才使用==判斷class實(shí)例。

因?yàn)榉瓷涞哪康氖菫榱双@得某個(gè)類(lèi)的實(shí)例對(duì)象的信息。因此,當(dāng)我們拿到某個(gè)Object對(duì)象時(shí),可以通過(guò)反射直接獲取該Objectclass信息,而不需要使用向下轉(zhuǎn)型

void printObjectInfo(Object obj) {
    Class cls = obj.getClass();
}

要從Class實(shí)例獲取獲取的基本信息,參考下面的代碼(只是簡(jiǎn)單示范,后面會(huì)具體介紹):

public class Main {
    public static void main(String[] args) {
        printClassInfo("".getClass());
        printClassInfo(Runnable.class);
        printClassInfo(java.time.Month.class);
        printClassInfo(String[].class);
        printClassInfo(int.class);
    }

    static void printClassInfo(Class cls) {
        System.out.println("Class name: " + cls.getName());
        System.out.println("Simple name: " + cls.getSimpleName());
        
        if (cls.getPackage() != null) {
            System.out.println("Package name: " + cls.getPackage().getName());
        }
        
        System.out.println("is interface: " + cls.isInterface());
        System.out.println("is enum: " + cls.isEnum());
        System.out.println("is array: " + cls.isArray());
        System.out.println("is primitive: " + cls.isPrimitive());
    }
}
  • 注意到數(shù)組(例如String[])也是一種類(lèi),而且不同于String.class,它的類(lèi)名是[Ljava.lang.String;。此外,JVM為每一種基本類(lèi)型如int也創(chuàng)建了Class實(shí)例,通過(guò)int.class訪問(wèn)。

如果獲取到了一個(gè)Class類(lèi)對(duì)象,我們就可以通過(guò)該Class類(lèi)對(duì)象來(lái)創(chuàng)建其對(duì)應(yīng)類(lèi)的實(shí)例對(duì)象:

// 獲取 String 的 Class 類(lèi)對(duì)象:
Class cls = String.class;
// 通過(guò) String 的 Class 類(lèi)對(duì)象創(chuàng)建一個(gè) String 類(lèi)的實(shí)例對(duì)象:
String s = (String) cls.newInstance();
  • 上述代碼相當(dāng)于new String()。通過(guò)Class.newInstance()可以創(chuàng)建類(lèi)的實(shí)例對(duì)象,它的局限是:只能調(diào)用public的無(wú)參數(shù)構(gòu)造方法。帶參數(shù)的構(gòu)造方法,或者非public的構(gòu)造方法都無(wú)法通過(guò)Class.newInstance()被調(diào)用。

1. 動(dòng)態(tài)加載

JVM在執(zhí)行 Java程序的時(shí)候,并不是一次性把所有用到的class全部加載到內(nèi)存,而是第一次需要用到class時(shí)才加載。例如:

public class Main {
    public static void main(String[] args) {
        if (args.length > 0) {
            create(args[0]);
        }
    }

    static void create(String name) {
        Person p = new Person(name);
    }
}
  • 當(dāng)執(zhí)行Main.java時(shí),由于用到了Main類(lèi),因此,JVM 首先會(huì)把Main類(lèi)對(duì)應(yīng)的Class類(lèi)對(duì)象Main.class加載到內(nèi)存中。然而,并不會(huì)加載Person.class,除非程序執(zhí)行到create()方法,JVM 發(fā)現(xiàn)需要加載Person類(lèi)時(shí),才會(huì)首次加載Person類(lèi)對(duì)應(yīng)的Class類(lèi)對(duì)象Person.class。如果沒(méi)有執(zhí)行create()方法,那么Person.class根本就不會(huì)被加載。

  • 這就是 JVM動(dòng)態(tài)加載class的特性。

動(dòng)態(tài)加載類(lèi)class的特性對(duì)于 Java 程序非常重要。利用 JVM 動(dòng)態(tài)加載class的特性,我們才能在運(yùn)行期根據(jù)條件去加載不同的實(shí)現(xiàn)類(lèi)。例如,Commons Logging 總是優(yōu)先使用 Log4j,只有當(dāng) Log4j 不存在時(shí),才使用 JDK 的 logging。利用 JVM 動(dòng)態(tài)加載特性,大致的實(shí)現(xiàn)代碼如下:

// Commons Logging優(yōu)先使用Log4j:
LogFactory factory = null;

if (isClassPresent("org.apache.logging.log4j.Logger")) {
    factory = createLog4j();
} else {
    factory = createJdkLog();
}

boolean isClassPresent(String name) {
    try {
        Class.forName(name);
        return true;
    } catch (Exception e) {
        return false;
    }
}
  • 這就是為什么我們只需要把 Log4j 的 jar 包放到 classpath 中,Commons Logging 就會(huì)自動(dòng)使用 Log4j 的原因。

2. 小結(jié)

  1. JVM為每個(gè)加載的類(lèi)class及接口interface創(chuàng)建了對(duì)應(yīng)的Class類(lèi)對(duì)象來(lái)保存classinterface的所有信息;
  2. 獲取一個(gè)類(lèi)class對(duì)應(yīng)的Class類(lèi)對(duì)象后,就可以獲取該類(lèi)class的所有信息;
  3. 通過(guò) Class類(lèi)對(duì)象獲取class信息的方法稱(chēng)為反射(Reflection);
  4. JVM 總是動(dòng)態(tài)加載class,可以在運(yùn)行期根據(jù)條件來(lái)控制加載類(lèi)class

三、訪問(wèn)字段

對(duì)任意的一個(gè)Object實(shí)例,只要我們獲取了它對(duì)應(yīng)的Class類(lèi)對(duì)象,就可以獲取它的一切信息。

我們先看看如何通過(guò)Class類(lèi)對(duì)象獲取其對(duì)應(yīng)的類(lèi)定義的字段信息。Class類(lèi)提供了以下幾個(gè)方法來(lái)獲取字段:

  1. Field getField(name):根據(jù)字段名獲取某個(gè) public 的 field(包括父類(lèi))

  2. Field getDeclaredField(name):根據(jù)字段名獲取當(dāng)前類(lèi)的某個(gè) field(不包括父類(lèi))

  3. Field[] getFields():獲取所有 public 的 field(包括父類(lèi))

  4. Field[] getDeclaredFields():獲取當(dāng)前類(lèi)的所有 field(不包括父類(lèi))

我們來(lái)看一下示例代碼:

public class Main {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 獲取public字段"score":
        System.out.println(stdClass.getField("score"));
        // 獲取繼承的public字段"name":
        System.out.println(stdClass.getField("name"));
        // 獲取private字段"grade":
        System.out.println(stdClass.getDeclaredField("grade"));
    }
}

class Student extends Person {
    public int score;
    private int grade;
}

class Person {
    public String name;
}
  • 上述代碼首先獲取StudentClass實(shí)例,然后,分別獲取public字段、繼承的public字段以及private字段,打印出的Field類(lèi)似下面:
public int Student.score
public java.lang.String Person.name
private int Student.grade
  • 一個(gè)Field對(duì)象包含了一個(gè)字段的所有信息:

    • getName():返回字段名稱(chēng),例如,"name";

    • getType():返回字段類(lèi)型,也是一個(gè)Class類(lèi)對(duì)象,例如,String.class

    • getModifiers():返回字段的修飾符,它是一個(gè)int,不同的 bit 表示不同的含義。

String類(lèi)的value字段為例,它的定義是:

public final class String {
    private final byte[] value;
}

我們用反射獲取該字段的信息,代碼如下:

Field f = String.class.getDeclaredField("value");
f.getName(); // "value"
f.getType(); // class [B 表示byte[]類(lèi)型

int m = f.getModifiers();
Modifier.isFinal(m); // true
Modifier.isPublic(m); // false
Modifier.isProtected(m); // false
Modifier.isPrivate(m); // true
Modifier.isStatic(m); // false

1. 獲取字段值

利用反射拿到字段的一個(gè)Field類(lèi)對(duì)象只是第一步,我們還可以拿到一個(gè)實(shí)例對(duì)象對(duì)應(yīng)的該字段的值。

例如,對(duì)于一個(gè)Person類(lèi)對(duì)象,我們可以先拿到其name字段對(duì)應(yīng)的Field,再獲取這個(gè)Person類(lèi)對(duì)象的name字段的 值:

import java.lang.reflect.Field;
public class Main {
    public static void main(String[] args) throws Exception {
        Person p = new Person("Xiao Ming");
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");// 獲取 private String name;
        Object value = f.get(p);
        System.out.println(value); // "Xiao Ming"
    }
}

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }
}
  • 上述代碼先獲取Person類(lèi)對(duì)應(yīng)的Class類(lèi)對(duì)象,再通過(guò)該Class類(lèi)對(duì)象獲取Field類(lèi)對(duì)象,然后,用Field.get(Object)獲取指定Person類(lèi)對(duì)象的指定字段的值。

  • 運(yùn)行代碼,如果不出意外,會(huì)得到一個(gè)IllegalAccessException異常,這是因?yàn)?code>name被定義為一個(gè)private字段,正常情況下,Main類(lèi)無(wú)法訪問(wèn)Person類(lèi)的private字段。要修復(fù)錯(cuò)誤,可以將private改為public,或者,在調(diào)用Object value = f.get(p);前,先寫(xiě)一句:

f.setAccessible(true);
  • 調(diào)用Field.setAccessible(true)的意思是,別管這個(gè)字段是不是public,一律允許訪問(wèn)。

  • 可以試著加上上述語(yǔ)句,再運(yùn)行代碼,就可以打印出private字段的值。

有童鞋會(huì)問(wèn):如果使用反射可以獲取private字段的值,那么類(lèi)的封裝還有什么意義?

  • 答案是一般情況下,我們總是通過(guò)p.name來(lái)訪問(wèn)Personname字段,編譯器會(huì)根據(jù)public、protectedprivate這些訪問(wèn)權(quán)限修飾符決定是否允許訪問(wèn)字段,這樣就達(dá)到了數(shù)據(jù)封裝的目的。

  • 而反射是一種非常規(guī)的用法,使用反射,首先代碼非常繁瑣;其次,它更多地是給工具或者底層框架來(lái)使用,目的是在不知道目標(biāo)對(duì)象任何信息的情況下,獲取特定字段的值。

此外,setAccessible(true)可能會(huì)失敗。 如果 JVM 運(yùn)行期存在SecurityManager,那么它會(huì)根據(jù)規(guī)則進(jìn)行檢查,有可能阻止setAccessible(true)。例如,某個(gè)SecurityManager可能不允許對(duì)javajavax開(kāi)頭的package的類(lèi)調(diào)用setAccessible(true),這樣可以保證 JVM 核心庫(kù)的安全。

2. 設(shè)置字段值

通過(guò) Field 類(lèi)對(duì)象既然可以獲取到指定對(duì)象的字段值,自然也可以設(shè)置字段的值。

設(shè)置字段值是通過(guò)Field.set(Object, Object)實(shí)現(xiàn)的,其中第一個(gè)Object參數(shù)是指定的對(duì)象,第二個(gè)Object參數(shù)是待修改的值。示例代碼如下:

import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws Exception {
        Person p = new Person("Xiao Ming");
        System.out.println(p.getName()); // "Xiao Ming"
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");// 獲取 private String name;
        f.setAccessible(true);// 允許對(duì) private 字段進(jìn)行訪問(wèn)
        f.set(p, "Xiao Hong");// 設(shè)置 p 的 name 的值
        System.out.println(p.getName()); // "Xiao Hong"
    }
}

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}
  • 運(yùn)行上述代碼,輸出的name字段從Xiao Ming變成了Xiao Hong,說(shuō)明通過(guò)反射可以直接修改指定對(duì)象的字段的值。

  • 同樣的,修改非public字段,需要調(diào)用setAccessible(true)

3. 小結(jié)

  1. Java 的反射 API 提供的Field類(lèi)封裝了對(duì)應(yīng)的類(lèi)定義的全部字段的所有信息:
  2. 通過(guò)Class類(lèi)對(duì)象的方法可以獲取Field類(lèi)對(duì)象:getField(),getFields(),getDeclaredField(),getDeclaredFields();
  3. 通過(guò)Field類(lèi)對(duì)象可以獲取類(lèi)定義字段信息:getName(),getType(),getModifiers();
  4. 通過(guò)Field類(lèi)對(duì)象可以讀取或設(shè)置某個(gè)對(duì)象的字段的值,如果存在訪問(wèn)限制,則需要調(diào)用setAccessible(true)來(lái)訪問(wèn)非public字段。
  5. 通過(guò)反射讀寫(xiě)字段是一種非常規(guī)的方法,它會(huì)破壞對(duì)象的封裝。

四、調(diào)用方法

我們已經(jīng)能通過(guò)Class類(lèi)的Field類(lèi)對(duì)象獲取其對(duì)應(yīng)的類(lèi)class中定義的所有字段信息,同樣的,可以通過(guò)Class類(lèi)獲取所有Method信息。Class類(lèi)提供了以下幾個(gè)方法來(lái)獲取類(lèi)class中定義的Method

  1. Method getMethod(name, Class...):獲取某個(gè)publicMethod(包括父類(lèi))
  2. Method getDeclaredMethod(name, Class...):獲取當(dāng)前類(lèi)的某個(gè)Method(不包括父類(lèi))
  3. Method[] getMethods():獲取所有publicMethod(包括父類(lèi))
  4. Method[] getDeclaredMethods():獲取當(dāng)前類(lèi)的所有Method(不包括父類(lèi))

我們來(lái)看一下示例代碼:

public class Main {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 獲取 public方法 getScore,形參類(lèi)型為 String:
        System.out.println(stdClass.getMethod("getScore", String.class));
        // 獲取繼承的 public方法 getName,無(wú)參數(shù):
        System.out.println(stdClass.getMethod("getName"));
        // 獲取 private方法 getGrade,形參類(lèi)型為 int:
        System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
    }
}

class Student extends Person {
    public int getScore(String type) {
        return 99;
    }
    private int getGrade(int year) {
        return 1;
    }
}

class Person {
    public String getName() {
        return "Person";
    }
}
  • 上述代碼首先獲取StudentClass類(lèi)對(duì)象,然后,分別獲取Student類(lèi)中定義的public方法、繼承的public方法以及private方法,打印出的Method類(lèi)似:
public int Student.getScore(java.lang.String)
public java.lang.String Person.getName()
private int Student.getGrade(int)

一個(gè)Method類(lèi)對(duì)象包含一個(gè)方法的所有信息:

  • getName():返回方法名稱(chēng),例如:"getScore";
  • getReturnType():返回方法的返回值類(lèi)型,也是一個(gè)Class實(shí)例,例如:String.class
  • getParameterTypes():返回方法的參數(shù)類(lèi)型,是一個(gè)Class數(shù)組,例如:{String.class, int.class};
  • getModifiers():返回方法的修飾符,它是一個(gè)int,不同的 bit 表示不同的含義。

1. 調(diào)用方法

當(dāng)我們獲取到一個(gè)Method類(lèi)對(duì)象時(shí),就可以對(duì)它進(jìn)行調(diào)用。我們以下面的代碼為例:

// 一般情況下調(diào)用 String 類(lèi)的 substring() 方法
String s = "Hello world";
String r = s.substring(6); // "world"

如果用反射來(lái)調(diào)用substring方法,需要以下代碼:

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        // String 對(duì)象:
        String s = "Hello world";
        // 獲取 String substring(int)方法,形參為 int:
        Method m = String.class.getMethod("substring", int.class);
        // 在 s 對(duì)象上調(diào)用該方法并獲取結(jié)果:
        String r = (String) m.invoke(s, 6);
        // 打印調(diào)用結(jié)果:
        System.out.println(r);
    }
}
  • 注意到substring()有兩個(gè)重載方法,我們獲取的是String substring(int)這個(gè)方法(即形參類(lèi)型為 int,且只有一個(gè))。思考一下如何獲取String substring(int, int)方法。

  • 對(duì)Method類(lèi)對(duì)象調(diào)用invoke方法就相當(dāng)于調(diào)用該substring(int)方法,invoke的第一個(gè)參數(shù)是實(shí)例對(duì)象(即在哪個(gè)實(shí)例對(duì)象上調(diào)用該方法),后面的實(shí)參要與方法參數(shù)的類(lèi)型一致,否則將報(bào)錯(cuò)。

2. 調(diào)用靜態(tài)方法

如果獲取到的Method表示一個(gè)靜態(tài)方法,調(diào)用靜態(tài)方法時(shí),由于無(wú)需指定實(shí)例對(duì)象,所以invoke方法傳入的第一個(gè)參數(shù)永遠(yuǎn)為null。我們以Integer.parseInt(String)方法為例:

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        // 獲取 Integer.parseInt(String) 方法,參數(shù)為 String:
        Method m = Integer.class.getMethod("parseInt", String.class);
        // 調(diào)用該靜態(tài)方法并獲取結(jié)果:
        Integer n = (Integer) m.invoke(null, "12345");
        // 打印調(diào)用結(jié)果:
        System.out.println(n);// 12345
    }
}

3. 調(diào)用非 public方法

Field類(lèi)對(duì)象類(lèi)似,對(duì)于非 public 方法,我們雖然可以通過(guò)Class.getDeclaredMethod()獲取該方法的實(shí)例對(duì)象,但直接對(duì)其調(diào)用將得到一個(gè)IllegalAccessException異常。為了調(diào)用非 public 方法,我們通過(guò)Method.setAccessible(true)允許其調(diào)用:

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        Method m = p.getClass().getDeclaredMethod("setName", String.class);
        m.setAccessible(true);
        m.invoke(p, "Bob");
        System.out.println(p.name);// Bob
    }
}

class Person {
    String name;
    
    private void setName(String name) {
        this.name = name;
    }
}
  • 同樣,setAccessible(true)可能會(huì)失敗。如果 JVM 運(yùn)行期存在SecurityManager,那么它會(huì)根據(jù)規(guī)則進(jìn)行檢查,有可能阻止setAccessible(true)。例如,某個(gè)SecurityManager可能不允許對(duì)javajavax開(kāi)頭的package的類(lèi)調(diào)用setAccessible(true),這樣可以保證 JVM 核心庫(kù)的安全。

4. 多態(tài)

我們來(lái)考率這樣一種情況:一個(gè)Person類(lèi)定義了hello()方法,并且它的子類(lèi)Student也重寫(xiě)了hello()方法,那么,從Person.class獲取的Method,作用于Student類(lèi)對(duì)象時(shí),調(diào)用的hello()方法到底是哪個(gè)?

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        // 獲取Person的 hello方法:
        Method h = Person.class.getMethod("hello");
        // 對(duì) Student實(shí)例調(diào)用 hello方法:
        h.invoke(new Student());
    }
}

class Person {
    public void hello() {
        System.out.println("Person:hello");
    }
}

class Student extends Person {
    public void hello() {
        System.out.println("Student:hello");
    }
}
  • 運(yùn)行上述代碼,發(fā)現(xiàn)輸出的是Student:hello,因此,使用反射調(diào)用方法時(shí),仍然遵循多態(tài)原則:即總是調(diào)用實(shí)際類(lèi)型的重寫(xiě)方法(如果存在)。 上述的反射代碼:
Method m = Person.class.getMethod("hello");
m.invoke(new Student());
  • 實(shí)際上相當(dāng)于:
Person p = new Student();
p.hello();

5. 小結(jié)

  1. Java 的反射 API 提供的Method類(lèi)對(duì)象封裝了類(lèi)定義的全部方法的所有信息:
  2. 通過(guò)Class類(lèi)對(duì)象的方法可以獲取Method類(lèi)對(duì)象:getMethod()getMethods(),getDeclaredMethod()getDeclaredMethods();
  3. 通過(guò)Method類(lèi)對(duì)象可以獲取方法信息:getName(),getReturnType(),getParameterTypes(),getModifiers();
  4. 通過(guò)Method類(lèi)對(duì)象可以調(diào)用某個(gè)對(duì)象的方法:Object invoke(Object instance, Object... parameters)
  5. 通過(guò)設(shè)置setAccessible(true)來(lái)訪問(wèn)非public方法;
  6. 通過(guò)反射調(diào)用方法時(shí),仍然遵循多態(tài)原則。

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

一般情況下,我們通常使用new操作符創(chuàng)建新的對(duì)象:

Person p = new Person();

如果通過(guò)反射來(lái)創(chuàng)建新的對(duì)象,可以調(diào)用Class提供的newInstance()方法:

Person p = Person.class.newInstance();
  • 調(diào)用Class.newInstance()的局限是,它只能調(diào)用該類(lèi)的public無(wú)參構(gòu)造方法。如果構(gòu)造方法帶有參數(shù),或者不是public,就無(wú)法直接通過(guò)Class.newInstance()來(lái)調(diào)用。

為了調(diào)用任意的構(gòu)造方法,Java 的反射 API 提供了Constructor類(lèi)對(duì)象,它包含一個(gè)構(gòu)造方法的所有信息,通過(guò)Constructor類(lèi)對(duì)象可以創(chuàng)建一個(gè)類(lèi)的實(shí)例對(duì)象。Constructor類(lèi)對(duì)象和Method類(lèi)對(duì)象非常相似,不同之處僅在于它是一個(gè)構(gòu)造方法,并且,調(diào)用結(jié)果總是返回一個(gè)類(lèi)的實(shí)例對(duì)象:

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws Exception {
        // 獲取構(gòu)造方法 Integer(int),形參為 int
        Constructor cons1 = Integer.class.getConstructor(int.class);
        // 調(diào)用構(gòu)造方法:
        // 傳入的形參必須與構(gòu)造方法的形參類(lèi)型相匹配
        Integer n1 = (Integer) cons1.newInstance(123);
        System.out.println(n1);

        // 獲取構(gòu)造方法Integer(String),形參為 String
        Constructor cons2 = Integer.class.getConstructor(String.class);
        Integer n2 = (Integer) cons2.newInstance("456");
        System.out.println(n2);
    }
}

通過(guò)Class實(shí)例獲取Constructor的方法如下:

  1. getConstructor(Class...):獲取某個(gè)publicConstructor;
  2. getDeclaredConstructor(Class...):獲取某個(gè)Constructor
  3. getConstructors():獲取所有publicConstructor;
  4. getDeclaredConstructors():獲取所有Constructor

注意:Constructor類(lèi)對(duì)象只含有當(dāng)前類(lèi)定義的構(gòu)造方法,和父類(lèi)無(wú)關(guān),因此不存在多態(tài)的問(wèn)題。

同樣,調(diào)用非publicConstructor時(shí),必須首先通過(guò)setAccessible(true)設(shè)置允許訪問(wèn)。但setAccessible(true)也可能會(huì)失敗。

小結(jié)

  1. Constructor類(lèi)對(duì)象封裝了其對(duì)應(yīng)的類(lèi)定義的構(gòu)造方法的所有信息;
  2. 通過(guò)Class類(lèi)對(duì)象可以獲取Constructor類(lèi)對(duì)象:getConstructor()getConstructors(),getDeclaredConstructor()getDeclaredConstructors();
  3. 通過(guò)Constructor類(lèi)對(duì)象可以創(chuàng)建一個(gè)對(duì)應(yīng)類(lèi)的實(shí)例對(duì)象:newInstance(Object... parameters); 通過(guò)設(shè)置setAccessible(true)來(lái)訪問(wèn)非public構(gòu)造方法。

六、獲取繼承方法

當(dāng)我們獲取到某個(gè)Class類(lèi)對(duì)象時(shí),實(shí)際上就獲取到了一個(gè)類(lèi)的類(lèi)型:

Class cls = String.class; // 獲取到 String 的 Class類(lèi)對(duì)象

還可以用類(lèi)對(duì)象的getClass()方法獲?。?/strong>

String s = "";
Class cls = s.getClass(); // s是String,因此獲取到String的Class

最后一種獲取Class的方法是通過(guò)Class.forName(""),傳入Class的完整類(lèi)名獲?。?/strong>

Class s = Class.forName("java.lang.String");

這三種方式獲取的Class類(lèi)對(duì)象都是同一個(gè)對(duì)象,因?yàn)?JVM 對(duì)每個(gè)加載的Class只創(chuàng)建一個(gè)Class類(lèi)對(duì)象來(lái)表示它的類(lèi)型。

1. 獲取父類(lèi)的Class

有了Class類(lèi)對(duì)象,我們還可以獲取它的父類(lèi)的Class類(lèi)對(duì)象:

public class Main {
    public static void main(String[] args) throws Exception {
        Class i = Integer.class;
        Class n = i.getSuperclass();
        System.out.println(n);
        Class o = n.getSuperclass();
        System.out.println(o);
        System.out.println(o.getSuperclass());
    }
}
  • 運(yùn)行上述代碼,可以看到,Integer的父類(lèi)類(lèi)型是Number,Number的父類(lèi)是ObjectObject的父類(lèi)是null。除Object外,其他任何非接口interfaceClass類(lèi)對(duì)象都必定存在一個(gè)父類(lèi)類(lèi)型。

2. 獲取interface

由于一個(gè)類(lèi)可能實(shí)現(xiàn)一個(gè)或多個(gè)接口,通過(guò)Class我們就可以查詢(xún)到實(shí)現(xiàn)的接口類(lèi)型。例如,查詢(xún)Integer實(shí)現(xiàn)的接口:

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Class s = Integer.class;
        Class[] is = s.getInterfaces();
        for (Class i : is) {
            System.out.println(i);
        }
    }
}

運(yùn)行上述代碼可知,Integer實(shí)現(xiàn)的接口有:

  • java.lang.Comparable
  • java.lang.constant.Constable
  • java.lang.constant.ConstantDesc

要特別注意:getInterfaces()方法只返回當(dāng)前類(lèi)直接實(shí)現(xiàn)的接口類(lèi)型,并不包括其父類(lèi)實(shí)現(xiàn)的接口類(lèi)型:

// reflection
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Class s = Integer.class.getSuperclass();
        Class[] is = s.getInterfaces();
        for (Class i : is) {
            System.out.println(i);
        }
    }
}
  • Integer的父類(lèi)是Number,Number類(lèi)實(shí)現(xiàn)的接口是java.io.Serializable。

此外,對(duì)所有接口interfaceClass類(lèi)對(duì)象調(diào)用getSuperclass()返回的是null,獲取接口的父接口要用getInterfaces()

System.out.println(java.io.DataInputStream.class.getSuperclass()); 
// 輸出 java.io.FilterInputStream。因?yàn)?DataInputStream 繼承自 FilterInputStream

System.out.println(java.io.Closeable.class.getSuperclass()); 
// 輸出 null。因?yàn)閷?duì)接口調(diào)用 getSuperclass()總是返回 null,獲取接口的父接口要用 getInterfaces()
  • 如果一個(gè)類(lèi)沒(méi)有實(shí)現(xiàn)任何interface,那么getInterfaces()返回空數(shù)組。

3. 繼承關(guān)系

當(dāng)我們判斷一個(gè)對(duì)象是否是某個(gè)類(lèi)型時(shí),正常情況下,使用instanceof操作符:

Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true
boolean isNumber = n instanceof Number; // true
boolean isSerializable = n instanceof java.io.Serializable; // true

如果是兩個(gè)Class類(lèi)對(duì)象,要判斷一個(gè)向上轉(zhuǎn)型是否成立,可以調(diào)用isAssignableFrom()方法:

// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因?yàn)镮nteger可以賦值給Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因?yàn)镮nteger可以賦值給Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因?yàn)镮nteger可以賦值給Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因?yàn)镹umber不能賦值給Integer

4. 小結(jié)

  1. 通過(guò)Class對(duì)象可以獲取繼承關(guān)系:

    • Class getSuperclass():獲取父類(lèi)類(lèi)型;

    • Class[] getInterfaces():獲取當(dāng)前類(lèi)實(shí)現(xiàn)的所有接口。

  2. 通過(guò)Class對(duì)象的isAssignableFrom()方法可以判斷一個(gè)向上轉(zhuǎn)型是否可以實(shí)現(xiàn)。

七、動(dòng)態(tài)代理

我們來(lái)比較 Java 的類(lèi)class和接口interface的區(qū)別:

  • 可以實(shí)例化類(lèi)class(非abstract);
  • 不能實(shí)例化接口interface

所有接口interface類(lèi)型的變量總是通過(guò)某個(gè)實(shí)現(xiàn)了接口的類(lèi)的對(duì)象向上轉(zhuǎn)型再賦值給接口類(lèi)型的變量:

CharSequence cs = new StringBuilder();

有沒(méi)有可能不編寫(xiě)實(shí)現(xiàn)類(lèi),直接在運(yùn)行期創(chuàng)建某個(gè)interface的實(shí)例呢?

這是可能的,因?yàn)?Java 標(biāo)準(zhǔn)庫(kù)提供了一種動(dòng)態(tài)代理(Dynamic Proxy)的機(jī)制:可以在運(yùn)行期動(dòng)態(tài)創(chuàng)建某個(gè)interface的實(shí)例。

什么叫運(yùn)行期動(dòng)態(tài)創(chuàng)建?聽(tīng)起來(lái)好像很復(fù)雜。所謂動(dòng)態(tài)代理,是和靜態(tài)相對(duì)應(yīng)的。我們來(lái)看靜態(tài)代理代碼怎么寫(xiě):

一、定義接口:

public interface Hello {
    void morning(String name);
}

二、編寫(xiě)實(shí)現(xiàn)類(lèi):

public class HelloWorld implements Hello {
    public void morning(String name) {
        System.out.println("Good morning, " + name);
    }
}

三、創(chuàng)建實(shí)例,轉(zhuǎn)型為接口并調(diào)用:

Hello hello = new HelloWorld();
hello.morning("Bob");
  • 這種方式就是我們通常編寫(xiě)代碼的方式。

還有一種方式是動(dòng)態(tài)代碼,我們?nèi)匀幌榷x了接口Hello,但是我們并不去編寫(xiě)實(shí)現(xiàn)類(lèi),而是直接通過(guò) JDK 提供的一個(gè)Proxy.newProxyInstance()方法創(chuàng)建了一個(gè)Hello接口對(duì)象。這種沒(méi)有實(shí)現(xiàn)類(lèi)但是在運(yùn)行期動(dòng)態(tài)創(chuàng)建了一個(gè)接口對(duì)象的方式,我們稱(chēng)為動(dòng)態(tài)代理。JDK 提供的動(dòng)態(tài)創(chuàng)建接口對(duì)象的方式,就叫動(dòng)態(tài)代理。

一個(gè)最簡(jiǎn)單的動(dòng)態(tài)代理實(shí)現(xiàn)如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                }
                return null;
            }
        };
        Hello hello = (Hello) Proxy.newProxyInstance(
            Hello.class.getClassLoader(), // 傳入ClassLoader
            new Class[] { Hello.class }, // 傳入要實(shí)現(xiàn)的接口
            handler); // 傳入處理調(diào)用方法的InvocationHandler
        hello.morning("Bob");
    }
}

interface Hello {
    void morning(String name);
}

在運(yùn)行期動(dòng)態(tài)創(chuàng)建一個(gè)interface實(shí)例的方法如下:

  1. 定義一個(gè)InvocationHandler實(shí)例,它負(fù)責(zé)實(shí)現(xiàn)接口的方法調(diào)用;
  2. 通過(guò)Proxy.newProxyInstance()創(chuàng)建interface實(shí)例,它需要3個(gè)參數(shù):
    1. 使用的ClassLoader,通常就是接口類(lèi)的ClassLoader;
    2. 需要實(shí)現(xiàn)的接口數(shù)組,至少需要傳入一個(gè)接口進(jìn)去;
    3. 用來(lái)處理接口方法調(diào)用的InvocationHandler實(shí)例。
  3. 將返回的Object強(qiáng)制轉(zhuǎn)型為接口。

動(dòng)態(tài)代理實(shí)際上是JVM在運(yùn)行期動(dòng)態(tài)創(chuàng)建class字節(jié)碼并加載的過(guò)程,它并沒(méi)有什么黑魔法,把上面的動(dòng)態(tài)代理改寫(xiě)為靜態(tài)實(shí)現(xiàn)類(lèi)大概長(zhǎng)這樣:

public class HelloDynamicProxy implements Hello {
    InvocationHandler handler;
    public HelloDynamicProxy(InvocationHandler handler) {
        this.handler = handler;
    }
    public void morning(String name) {
        handler.invoke(
           this,
           Hello.class.getMethod("morning", String.class),
           new Object[] { name });
    }
}
  • 其實(shí)就是 JVM 幫我們自動(dòng)編寫(xiě)了一個(gè)上述類(lèi)(不需要源碼,可以直接生成字節(jié)碼),并不存在可以直接實(shí)例化接口的黑魔法。

小結(jié)

  1. Java 標(biāo)準(zhǔn)庫(kù)提供了動(dòng)態(tài)代理功能,允許在運(yùn)行期動(dòng)態(tài)創(chuàng)建一個(gè)接口的實(shí)例;

  2. 動(dòng)態(tài)代理是通過(guò)Proxy創(chuàng)建代理對(duì)象,然后將接口方法“代理”給InvocationHandler完成的。

?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-753245.html

到了這里,關(guān)于Java 中的反射機(jī)制(兩萬(wàn)字超全詳解)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶(hù)投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Java反射機(jī)制,動(dòng)態(tài)代理,hook以及在Retrofit源碼中的應(yīng)用

    Java反射機(jī)制,動(dòng)態(tài)代理,hook以及在Retrofit源碼中的應(yīng)用

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

    2024年02月13日
    瀏覽(17)
  • Java筆記040-反射/反射機(jī)制、Class類(lèi)

    Java筆記040-反射/反射機(jī)制、Class類(lèi)

    目錄 反射(reflection) 一個(gè)需求引出反射 反射機(jī)制 Java反射機(jī)制原理圖 Java反射機(jī)制可以完成 反射相關(guān)的主要類(lèi) 反射機(jī)制的優(yōu)點(diǎn)和缺點(diǎn) 反射調(diào)用優(yōu)化-關(guān)閉訪問(wèn)檢查 Class類(lèi) 基本介紹 代碼解釋部分 類(lèi)加載方法 應(yīng)用實(shí)例:Class02.java 獲取Class類(lèi)對(duì)象 代碼解釋部分 哪些類(lèi)型有Class對(duì)象

    2024年02月09日
    瀏覽(20)
  • Java JVM中的GC機(jī)制詳解

    垃圾回收(Garbage Collection,簡(jiǎn)稱(chēng)GC)機(jī)制是JVM中最重要的部分之一。在Java程序運(yùn)行的過(guò)程中,運(yùn)行時(shí)數(shù)據(jù)區(qū)域(包括堆和棧等內(nèi)存區(qū)域)一直都需要使用和回收內(nèi)存空間。由于Java中的內(nèi)存分配方式是動(dòng)態(tài)的,所以在程序運(yùn)行期間,其內(nèi)存空間的占用量會(huì)不斷變化。 如果Java程

    2024年02月14日
    瀏覽(16)
  • Java安全基礎(chǔ)之Java反射機(jī)制和ClassLoader類(lèi)加載機(jī)制

    Java安全基礎(chǔ)之Java反射機(jī)制和ClassLoader類(lèi)加載機(jī)制

    目錄 Java 反射機(jī)制 反射 java.lang.Runtime ClassLoader 類(lèi)加載機(jī)制 URLClassLoader loadClass() 與 Class.forName() 的區(qū)別? Java 反射(Reflection)是 Java 非常重要的動(dòng)態(tài)特性。在運(yùn)行狀態(tài)中,通過(guò) Java 的反射機(jī)制,我們能夠判斷一個(gè)對(duì)象所屬的類(lèi)。了解任意一個(gè)類(lèi)的所有屬性和方法。能夠調(diào)用任

    2024年04月22日
    瀏覽(41)
  • Java反射、代理機(jī)制

    官方解釋?zhuān)悍瓷湓试S對(duì)封裝類(lèi)的字段、方法和構(gòu)造方法的信息進(jìn)行編程訪問(wèn)。 虛擬機(jī)加載類(lèi)文件后,會(huì)在方法區(qū)生成一個(gè)類(lèi)對(duì)象,包含了類(lèi)的結(jié)構(gòu)信息,如字段、方法、構(gòu)造方法等。反射是一種能夠在程序運(yùn)行時(shí)動(dòng)態(tài)訪問(wèn)、修改類(lèi)對(duì)象中任意屬性的機(jī)制(包括private屬性)。

    2024年02月10日
    瀏覽(17)
  • Java的反射機(jī)制

    Java 的反射機(jī)制允許在程序運(yùn)行期間,借助反射 API 獲取類(lèi)的內(nèi)部信息,并能直接操作對(duì)象的內(nèi)部屬性及方法。 Java 反射機(jī)制提供的功能: 在運(yùn)行時(shí),使用反射分析類(lèi)的能力,獲取有關(guān)類(lèi)的一切信息(類(lèi)所在的包、類(lèi)實(shí)現(xiàn)的接口、標(biāo)注的注解、類(lèi)的數(shù)據(jù)域、類(lèi)的構(gòu)造器、類(lèi)的

    2024年02月02日
    瀏覽(22)
  • Java的反射機(jī)制(2)

    目錄 Class類(lèi)基本介紹 Class類(lèi)的常用方法 如何獲取class類(lèi)對(duì)象 哪些類(lèi)型有Class對(duì)象 Class類(lèi)基本介紹 在Java語(yǔ)言中,每個(gè)對(duì)象都有一個(gè)運(yùn)行時(shí)類(lèi),即其所屬的類(lèi)。而這個(gè)運(yùn)行時(shí)類(lèi)在Java中是以Class類(lèi)的實(shí)例形式存在的,該Class類(lèi)實(shí)例就是所謂的Class對(duì)象。Class類(lèi)表示一個(gè)類(lèi)或接口的元

    2024年02月08日
    瀏覽(20)
  • Java反射機(jī)制是什么?

    Java反射機(jī)制是什么?

    Java 反射機(jī)制 是 Java 語(yǔ)言的一個(gè)重要特性。 在學(xué)習(xí) Java 反射機(jī)制前,大家應(yīng)該先了解兩個(gè)概念,編譯期和運(yùn)行期。 編譯期 是指把源碼交給編譯器編譯成計(jì)算機(jī)可以執(zhí)行的文件的過(guò)程。在 Java 中也就是把 Java 代碼編成 class 文件的過(guò)程。編譯期只是做了一些翻譯功能,并沒(méi)有把

    2024年02月12日
    瀏覽(11)
  • 【JavaSE】Java的反射機(jī)制

    1.java反射機(jī)制 1.1簡(jiǎn)介 被視為動(dòng)態(tài)語(yǔ)言的關(guān)鍵,允許程序在執(zhí)行期間,借助于RefectionAPI取得任何類(lèi)的內(nèi)部信息。在程序的運(yùn)行狀態(tài)中,可以構(gòu)造任意一個(gè)類(lèi)的對(duì)象,可以了解任意一個(gè)類(lèi)對(duì)象所屬的類(lèi),可以了解任意一個(gè)類(lèi)的成員變量和方法,可以調(diào)用任意一個(gè)對(duì)象的屬性和方

    2024年04月26日
    瀏覽(19)
  • Java學(xué)習(xí)路線(23)——反射機(jī)制

    一、概述 (一)什么是反射: 反射指的是任何一個(gè)Class類(lèi),在“運(yùn)行時(shí)”都可以直接得到全部成分。 (二)動(dòng)態(tài)可獲取的對(duì)象: 構(gòu)造器對(duì)象——Constructor,成員變量對(duì)象——Field,成員方法對(duì)象——Method。 (三)反射關(guān)鍵: 第一步都是得到編譯后的Class對(duì)象,然后可以獲得

    2024年02月08日
    瀏覽(15)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包