一、反射概述
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ī)制原理示意圖:
3. 反射優(yōu)點(diǎn)和缺點(diǎn)
- 優(yōu)點(diǎn):可以
動(dòng)態(tài)
地創(chuàng)建和使用對(duì)象,反射機(jī)制是 Java 框架的底層核心,其使用靈活,沒(méi)有反射機(jī)制,底層框架就失去支撐。- 缺點(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)加載。
靜態(tài)加載:編譯時(shí)就加載相關(guān)的類(lèi),如果程序中不存在該類(lèi)則編譯報(bào)錯(cuò),依賴(lài)性太強(qiáng)。
動(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ī):
- 靜態(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ì)被加載;
- 動(dòng)態(tài)加載
- 通過(guò)反射的方式,在程序運(yùn)行時(shí)使用到哪個(gè)類(lèi),該類(lèi)才會(huì)被加載;
類(lèi)加載的過(guò)程圖:
5. 類(lèi)加載各階段完成的功能
- 加載階段:將類(lèi)的 class 文件讀入內(nèi)存,并為之創(chuàng)建一個(gè) java.lang.Class 對(duì)象,此過(guò)程由類(lèi)加載器完成。
- 連接階段:又分為驗(yàn)證、準(zhǔn)備、解析三個(gè)小階段,此階段會(huì)將類(lèi)的二進(jìn)制數(shù)據(jù)合并到 JRE 中。
- 初始化階段:JVM 負(fù)責(zé)對(duì)類(lèi)的
靜態(tài)成員
進(jìn)行初始化。
如下圖所示:
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)證
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ō)明:
-
n1 是實(shí)例屬性, 不是靜態(tài)變量,因此在準(zhǔn)備階段,是不會(huì)分配內(nèi)存
-
n2 是靜態(tài)變量,在該階段 JVM 會(huì)為其分配內(nèi)存,n2 默認(rèn)初始化的值為 0 ,而不是 20
-
n3 被 static final 修飾,是常量, 它和靜態(tài)變量不一樣, 其一旦賦值后值就不變,因此其默認(rèn)初始化 n3 = 30
-
5.4 連接階段——解析
JVM 將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程。
5.5 初始化階段
- 在初始化階段,JVM 才會(huì)真正執(zhí)行類(lèi)中定義的 Java程序代碼,此階段是執(zhí)行<clinit>() 方法的過(guò)程。
- <clinit>() 方法是由編譯器按語(yǔ)句在源文件中出現(xiàn)的順序,依次自動(dòng)收集類(lèi)中的所有
靜態(tài)變量
的賦值操作和靜態(tài)代碼塊
中的語(yǔ)句,并進(jìn)行合并的過(guò)程。- 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ō)明:
- 加載階段:加載 B類(lèi),并生成 B的 class對(duì)象
- 連接階段:進(jìn)行默認(rèn)初始化 num = 0
- 初始化階段:執(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)
-
Class
也是一個(gè)類(lèi),其類(lèi)名就叫Class
,因此它也繼承 Object 類(lèi) -
Class
類(lèi)對(duì)象不是由我們程序員創(chuàng)建(new)出來(lái)的,而是在類(lèi)加載時(shí)由 JVM 自動(dòng)創(chuàng)建的 - 在
堆內(nèi)存
中最多只會(huì)存在某個(gè)類(lèi)的唯一的Class
對(duì)象,因?yàn)轭?lèi)只會(huì)加載一次 - 每個(gè)類(lèi)的實(shí)例對(duì)象都會(huì)知道自己對(duì)應(yīng)的
Class
對(duì)象 - 通過(guò)
Class
類(lèi)對(duì)象可以完整地得到其對(duì)應(yīng)的類(lèi)的信息,通過(guò)一系列反射 API - 類(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)型(class
或interface
):
┌───────────────────────────┐
│ 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è)class
的Class
實(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īng)該用
因?yàn)榉瓷涞哪康氖菫榱双@得某個(gè)類(lèi)的實(shí)例對(duì)象的信息。因此,當(dāng)我們拿到某個(gè)
Object
對(duì)象時(shí),可以通過(guò)反射直接獲取該Object
的class
信息,而不需要使用向下轉(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é)
- JVM為每個(gè)加載的類(lèi)
class
及接口interface
創(chuàng)建了對(duì)應(yīng)的Class
類(lèi)對(duì)象來(lái)保存class
及interface
的所有信息; - 獲取一個(gè)類(lèi)
class
對(duì)應(yīng)的Class
類(lèi)對(duì)象后,就可以獲取該類(lèi)class
的所有信息; - 通過(guò) Class類(lèi)對(duì)象獲取
class
信息的方法稱(chēng)為反射(Reflection); - 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)獲取字段:
Field getField(name)
:根據(jù)字段名獲取某個(gè) public 的 field(包括父類(lèi))
Field getDeclaredField(name)
:根據(jù)字段名獲取當(dāng)前類(lèi)的某個(gè) field(不包括父類(lèi))
Field[] getFields()
:獲取所有 public 的 field(包括父類(lèi))
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;
}
- 上述代碼首先獲取
Student
的Class
實(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)Person
的name
字段,編譯器會(huì)根據(jù)public
、protected
和private
這些訪問(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ì)java
和javax
開(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é)
- Java 的反射 API 提供的
Field
類(lèi)封裝了對(duì)應(yīng)的類(lèi)定義的全部字段的所有信息: - 通過(guò)
Class
類(lèi)對(duì)象的方法可以獲取Field
類(lèi)對(duì)象:getField()
,getFields()
,getDeclaredField()
,getDeclaredFields()
; - 通過(guò)
Field
類(lèi)對(duì)象可以獲取類(lèi)定義字段信息:getName()
,getType()
,getModifiers()
; - 通過(guò)
Field
類(lèi)對(duì)象可以讀取或設(shè)置
某個(gè)對(duì)象的字段的值,如果存在訪問(wèn)限制,則需要調(diào)用setAccessible(true)
來(lái)訪問(wèn)非public
字段。 - 通過(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
:
Method getMethod(name, Class...)
:獲取某個(gè)public
的Method
(包括父類(lèi))Method getDeclaredMethod(name, Class...)
:獲取當(dāng)前類(lèi)的某個(gè)Method
(不包括父類(lèi))Method[] getMethods()
:獲取所有public
的Method
(包括父類(lèi))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";
}
}
- 上述代碼首先獲取
Student
的Class
類(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ì)java
和javax
開(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é)
- Java 的反射 API 提供的
Method
類(lèi)對(duì)象封裝了類(lèi)定義的全部方法的所有信息: - 通過(guò)
Class
類(lèi)對(duì)象的方法可以獲取Method
類(lèi)對(duì)象:getMethod()
,getMethods()
,getDeclaredMethod()
,getDeclaredMethods()
; - 通過(guò)
Method
類(lèi)對(duì)象可以獲取方法信息:getName()
,getReturnType()
,getParameterTypes()
,getModifiers()
; - 通過(guò)
Method
類(lèi)對(duì)象可以調(diào)用某個(gè)對(duì)象的方法:Object invoke(Object instance, Object... parameters)
; - 通過(guò)設(shè)置
setAccessible(true)
來(lái)訪問(wèn)非public
方法; - 通過(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的方法如下:
-
getConstructor(Class...)
:獲取某個(gè)public
的Constructor
; -
getDeclaredConstructor(Class...)
:獲取某個(gè)Constructor
; -
getConstructors()
:獲取所有public
的Constructor
; -
getDeclaredConstructors()
:獲取所有Constructor
。
注意:Constructor
類(lèi)對(duì)象只含有當(dāng)前類(lèi)定義的構(gòu)造方法,和父類(lèi)無(wú)關(guān),因此不存在多態(tài)的問(wèn)題。
同樣,調(diào)用非public
的Constructor
時(shí),必須首先通過(guò)setAccessible(true)
設(shè)置允許訪問(wèn)。但setAccessible(true)
也可能會(huì)失敗。
小結(jié)
-
Constructor
類(lèi)對(duì)象封裝了其對(duì)應(yīng)的類(lèi)定義的構(gòu)造方法的所有信息; - 通過(guò)
Class
類(lèi)對(duì)象可以獲取Constructor
類(lèi)對(duì)象:getConstructor()
,getConstructors()
,getDeclaredConstructor()
,getDeclaredConstructors()
; - 通過(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)是Object
,Object
的父類(lèi)是null
。除Object
外,其他任何非接口interface
的Class
類(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ì)所有接口
interface
的Class
類(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é)
-
通過(guò)
Class
對(duì)象可以獲取繼承關(guān)系:-
Class getSuperclass()
:獲取父類(lèi)類(lèi)型; -
Class[] getInterfaces()
:獲取當(dāng)前類(lèi)實(shí)現(xiàn)的所有接口。
-
-
通過(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í)例的方法如下:
- 定義一個(gè)
InvocationHandler
實(shí)例,它負(fù)責(zé)實(shí)現(xiàn)接口的方法調(diào)用; - 通過(guò)
Proxy.newProxyInstance()
創(chuàng)建interface
實(shí)例,它需要3個(gè)參數(shù):- 使用的
ClassLoader
,通常就是接口類(lèi)的ClassLoader
; - 需要實(shí)現(xiàn)的接口數(shù)組,至少需要傳入一個(gè)接口進(jìn)去;
- 用來(lái)處理接口方法調(diào)用的
InvocationHandler
實(shí)例。
- 使用的
- 將返回的
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é)
-
Java 標(biāo)準(zhǔn)庫(kù)提供了動(dòng)態(tài)代理功能,允許在運(yùn)行期動(dòng)態(tài)創(chuàng)建一個(gè)接口的實(shí)例;
-
動(dòng)態(tài)代理是通過(guò)
Proxy
創(chuàng)建代理對(duì)象,然后將接口方法“代理”給InvocationHandler
完成的。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-753245.html
?文章來(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)!