最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Java的十五篇:反射

2023-03-03 00:06 作者:小劉Java之路  | 我要投稿

音樂的浪漫之處在于:它能將封存的記憶迅速拼湊起來,你會(huì)清晰的記起當(dāng)時(shí),聽這首歌的感覺和狀態(tài),就像時(shí)空真的倒回某一刻。


搖滾樂隊(duì)


反射是框架設(shè)計(jì)的靈魂

(使用的前提條件:必須先得到代表的字節(jié)碼的Class,Class類用于表示.class文件(字節(jié)碼))

一、反射的概述


JAVA反射機(jī)制是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性;這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為java語言的反射機(jī)制。

要想解剖一個(gè)類,必須先要獲取到該類的字節(jié)碼文件對(duì)象。而解剖使用的就是Class類中的方法.所以先要獲取到每一個(gè)字節(jié)碼文件對(duì)應(yīng)的Class類型的對(duì)象.

以上的總結(jié)就是什么是反射

反射就是把java類中的各種成分映射成一個(gè)個(gè)的Java對(duì)象

例如:一個(gè)類有:成員變量、方法、構(gòu)造方法、包等等信息,利用反射技術(shù)可以對(duì)一個(gè)類進(jìn)行解剖,把個(gè)個(gè)組成部分映射成一個(gè)個(gè)對(duì)象。

(其實(shí):一個(gè)類中這些成員方法、構(gòu)造方法、在加入類中都有一個(gè)類來描述)

如圖是類的正常加載過程:反射的原理在與class對(duì)象。

熟悉一下加載的時(shí)候:Class對(duì)象的由來是將class文件讀入內(nèi)存,并為之創(chuàng)建一個(gè)Class對(duì)象。


反方向的鐘


其中這個(gè)Class對(duì)象很特殊。我們先了解一下這個(gè)Class類




Java?反射機(jī)制對(duì)于小白來說,真的是一道巨大的坎兒,其他的東西吧,無非就是內(nèi)容多點(diǎn),多看看多背背就好了,反射真的就是不管看了多少遍不理解就還是不理解,而且學(xué)校里面的各大教材應(yīng)該都沒有反射這個(gè)章節(jié),有也是一帶而過。說實(shí)話,在這篇文章之前,我對(duì)反射也并非完全了解,畢竟平常開發(fā)基本用不到,不過,看完這篇文章相信你對(duì)反射就沒啥疑點(diǎn)了。



Class類的動(dòng)態(tài)加載類

如何動(dòng)態(tài)加載一個(gè)類呢?首先我們需要區(qū)分什么是動(dòng)態(tài)加載?什么是靜態(tài)加載?我們普遍認(rèn)為編譯時(shí)刻加載的類是靜態(tài)加載類,運(yùn)行時(shí)刻加載的類是動(dòng)態(tài)加載類。我們舉一個(gè)例子:

Class A{Public static void main(String[] args){if("B".equal(args[0])){B b=new B();b.start();}if("C".equal(args[0])){ ? ?C c=new C(); ? C.start(); ? ? ?} ? }}

上面這一段代碼,當(dāng)我們?cè)谟胑clipse或者myeclipse的時(shí)候我們并不關(guān)心是否能夠通過編譯,當(dāng)我們直接在cmd使用javac訪問A.java類的時(shí)候,就會(huì)拋出問題:

A.java:7:錯(cuò)誤:找不到符號(hào)B b=new B();符號(hào): ?類B位置:類AA.java:7:錯(cuò)誤:找不到符號(hào)B b=new B();符號(hào): ?類B位置:類AA.java:12:錯(cuò)誤:找不到符號(hào)C c=new C();符號(hào): ?類C位置:類AA.java:12:錯(cuò)誤:找不到符號(hào)C c=new C();符號(hào): ?類C位置:類A4個(gè)錯(cuò)誤

或許我們理所當(dāng)然的認(rèn)為這樣應(yīng)該是錯(cuò),類B根本就不存在。但是如果我們多思考一下,就會(huì)發(fā)現(xiàn)B一定用嗎?不一定。C一定用嗎?也不一定。那么好,現(xiàn)在我們就讓B類存在

Class B{Public static void start(){System.out.print("B...satrt"); ? }}

現(xiàn)在我們就先 javac B.class,讓B類先開始編譯。然后在運(yùn)行javac A.class。結(jié)果是:

A.java:12:錯(cuò)誤:找不到符號(hào)C c=new C();符號(hào): ?類C位置:類AA.java:12:錯(cuò)誤:找不到符號(hào)C c=new C();符號(hào): ?類C位置:類A2個(gè)錯(cuò)誤

我們?cè)傧耄@個(gè)程序有什么問題。如果你說沒有什么問題?C類本來就不存在?。∧敲磫栴}來了B類已經(jīng)存在了,假設(shè)我現(xiàn)在就想用B,我們這個(gè)程序用得了嗎?答案是肯定的,用不了。那用不了的原因是什么?因?yàn)槲覀冞@個(gè)程序是做的類的靜態(tài)加載,也就是說new創(chuàng)建對(duì)象是靜態(tài)加載類,在編譯時(shí)刻就需要加載所有的,可能使用到的類。所以不管你用不用這個(gè)類?,F(xiàn)在B類是存在的,但是我們這個(gè)程序仍然用不了,因?yàn)闀?huì)一直報(bào)C類有問題,所以B類我也用不了。那么在實(shí)際應(yīng)用當(dāng)中,我們肯定需要如果B類存在,B類我就能用,當(dāng)用C類的時(shí)候,你再告訴我錯(cuò)了。如果說將來你有100個(gè)類,只要其中一個(gè)類出現(xiàn)問題,其它99個(gè)類你都用不了。所以這并不是我們想要的。我們想要的就是我用那個(gè)類就加載那個(gè)類,也就是常說的運(yùn)行時(shí)刻加載,動(dòng)態(tài)加載類。如何實(shí)現(xiàn)動(dòng)態(tài)加載類呢?我們可以建這么一個(gè)類

Class All{Public static void start(){try{Class cl= Class.forName(args[0]);//通過類類型,創(chuàng)建該類的對(duì)象cl.newInstance();}catch(Exception e){e.printStackTrace();}}}

前面我們?cè)诜治鯟lass實(shí)例化對(duì)象的方式的時(shí)候,Class.forName("類的全稱"),它不僅僅表示了類的類類型,還表示了動(dòng)態(tài)加載類。當(dāng)我們javac All.java的時(shí)候,它不會(huì)報(bào)任何錯(cuò)誤,也就是說在編譯的時(shí)候是沒有錯(cuò)誤的。只有當(dāng)我們具體用某個(gè)類的時(shí)候,那個(gè)類不存在,它才會(huì)報(bào)錯(cuò)。如果加載的類是B類,就需要:

B bt = (B) cl.newInstance();

萬一加載的是C類呢,可以改成

C ct = (C) cl.newInstance();

但是如果我想用很多的類或者加載很多的類,該怎么辦?我們可以統(tǒng)一一個(gè)標(biāo)準(zhǔn),不論C類還是B類或者其他的類,比如定義一個(gè)標(biāo)準(zhǔn)Stand s = (Stand) cl.newInstance();

只要B類和C類都是這個(gè)標(biāo)準(zhǔn)的就行了。

Class All{Public static void start(){try{Class cl= Class.forName(args[0]);//通過類類型,創(chuàng)建該類的對(duì)象Stand s = (Stand) cl.newInstance();s.start();}catch(Exception e){e.printStackTrace();}}}interface Stand {Public void start();}

現(xiàn)在如果我想要用B類,我們只需要:

Class B implements Stand{Public void start(){System.out.print("B...satrt");}}

加載B類,編譯運(yùn)行。

javac B.javajavac Stand.javajava Stand B

結(jié)果:B...satrt

如果以后想用某一個(gè)類,不需要重新編譯,只需要實(shí)現(xiàn)這個(gè)標(biāo)準(zhǔn)的接口即可。只需要?jiǎng)討B(tài)的加載新的東西就行了。這就是動(dòng)態(tài)加載類。



1. 拋磚引玉:為什么要使用反射

前文我們說過,接口的使用提高了代碼的可維護(hù)性和可擴(kuò)展性,并且降低了代碼的耦合度。

來看個(gè)例子:

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

publicclass Test {
? ?
? ?interface X {
? ? public void test();
}

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

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

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

publicclass Test { ? ?

? ?......

public static void main(String[] args) {
? ? ? ?X a = create1("A");
? ? ? ?a.test();
? ? ? ?X b = create1("B");
? ? ? ?b.test();
? ?}

? ?public static X create1(String name){
? ? ? ?if (name.equals("A")) {
? ? ? ? ? ?returnnew A();
? ? ? ?} elseif(name.equals("B")){
? ? ? ? ? ?returnnew B();
? ? ? ?}
? ? ? ?returnnull;
? ?}

}

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

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

publicclass Test {

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

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

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

  • 在運(yùn)行時(shí)(動(dòng)態(tài)編譯)獲知任意一個(gè)對(duì)象所屬的類。

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

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

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

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



2. 理解 Class 類

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

img

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

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

img

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

img

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

img

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

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

img

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

img



3. 獲取 Class 類對(duì)象的四種方式

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

img

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

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

img

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

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


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

img

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

第三種:通過對(duì)象實(shí)例?instance.getClass()?獲取

img

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

img

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



4. 通過反射構(gòu)造一個(gè)類的實(shí)例

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

① 使用 Class.newInstance

舉個(gè)例子:

img

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

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

img

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

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

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

img

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

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

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

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

img

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

img

單個(gè)獲取構(gòu)造函數(shù)

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

img

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

img

舉個(gè)例子:

package fanshe;

publicclass Student {
//(默認(rèn)的構(gòu)造方法)
Student(String str){
?System.out.println("(默認(rèn))的構(gòu)造方法 s = " + str);
}
// 無參構(gòu)造方法
public Student(){
?System.out.println("調(diào)用了公有、無參構(gòu)造方法執(zhí)行了。。。");
}
// 有一個(gè)參數(shù)的構(gòu)造方法
public Student(char name){
?System.out.println("姓名:" + name);
}
// 有多個(gè)參數(shù)的構(gòu)造方法
public Student(String name ,int age){
?System.out.println("姓名:"+name+"年齡:"+ age);//這的執(zhí)行效率有問題,以后解決。
}
// 受保護(hù)的構(gòu)造方法
protected Student(boolean n){
?System.out.println("受保護(hù)的構(gòu)造方法 n = " + n);
}
// 私有構(gòu)造方法
private Student(int age){
?System.out.println("私有的構(gòu)造方法年齡:"+ age);
}
}

----------------------------------
? ?
publicclass Constructors {
public static void main(String[] args) throws Exception {
?// 加載Class對(duì)象
?Class clazz = Class.forName("fanshe.Student");
? ? ? ?
?// 獲取所有公有構(gòu)造方法
?Constructor[] conArray = clazz.getConstructors();
?for(Constructor c : conArray){
? System.out.println(c);
?}
? ? ? ?
?// 獲取所有的構(gòu)造方法(包括:私有、受保護(hù)、默認(rèn)、公有)
?conArray = clazz.getDeclaredConstructors();
?for(Constructor c : conArray){
? System.out.println(c);
?}
? ? ? ?
?// 獲取公有、無參的構(gòu)造方法
? ? ? ?// 因?yàn)槭菬o參的構(gòu)造方法所以類型是一個(gè)null,不寫也可以:這里需要的是一個(gè)參數(shù)的類型,切記是類型
?// 返回的是描述這個(gè)無參構(gòu)造函數(shù)的類對(duì)象。
?Constructor con = clazz.getConstructor(null);
?Object obj = con.newInstance(); // 調(diào)用構(gòu)造方法
?
?// 獲取私有構(gòu)造方法
?con = clazz.getDeclaredConstructor(int.class);
?System.out.println(con);
?con.setAccessible(true); // 為了調(diào)用 private 方法/域 我們需要取消安全檢查
?obj = con.newInstance(12); // 調(diào)用構(gòu)造方法
}
}

③ 使用開源庫 Objenesis

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

publicclass Test {
? ?// 不存在無參構(gòu)造函數(shù)
? ?privateint i;
? ?public Test(int i){
? ? ? ?this.i = i;
? ?}
? ?public void show(){
? ? ? ?System.out.println("test..." + i);
? ?}
}

------------------------
? ?
public static void main(String[] args) {
? ? ? ?Objenesis objenesis = new ObjenesisStd(true);
? ? ? ?Test test = objenesis.newInstance(Test.class);
? ? ? ?test.show();
? ?}

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



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

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

批量獲取

1)獲取所有公有的字段

img

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

img

單個(gè)獲取

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

img

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

img

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

img

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

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

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

舉個(gè)例子:

package fanshe.method;

publicclass Student {
public void show1(String s){
?System.out.println("調(diào)用了:公有的,String參數(shù)的show1(): s = " + s);
}
protected void show2(){
?System.out.println("調(diào)用了:受保護(hù)的,無參的show2()");
}
void show3(){
?System.out.println("調(diào)用了:默認(rèn)的,無參的show3()");
}
private String show4(int age){
?System.out.println("調(diào)用了,私有的,并且有返回值的,int參數(shù)的show4(): age = " + age);
?return"abcd";
}
}

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



7. 反射機(jī)制優(yōu)缺點(diǎn)

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

缺點(diǎn)

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

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



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

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

  • 動(dòng)態(tài)代理機(jī)制

  • 使用 JDBC 連接數(shù)據(jù)庫

  • Spring / Hibernate 框架(實(shí)際上是因?yàn)槭褂昧藙?dòng)態(tài)代理,所以才和反射機(jī)制有關(guān))

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

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

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

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

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

  • 通過 Connection 接口接收連接

  • 關(guān)閉連接

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

Spring 框架

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

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

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

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

另外,Spring AOP 由于使用了動(dòng)態(tài)代理,所以也使用了反射機(jī)制,這點(diǎn)我會(huì)在 Spring 的系列文章中詳細(xì)解釋。


jdk 的class

所以拿到這個(gè)類后,就相當(dāng)于拿到了咱們想解剖的類,那怎么拿到這個(gè)類?

看API文檔后,有一個(gè)方法forName(String className); 而且是一個(gè)靜態(tài)的方法,這樣咱們就可以得到想反射的類了

到這里,看Class clazz = Class.forName("com.cj.test.Person");這個(gè)應(yīng)該有點(diǎn)感覺了吧

Class.forName("com.cj.test.Person");因?yàn)檫@個(gè)方法里接收的是個(gè)字符串,字符串的話,我們就可以寫在配置文件里,然后利用反射生成我們需要的對(duì)象,這才是我們想要的。很多框架里都有類似的配置



擴(kuò)展:

1、除了上述的Spring配置文件里會(huì)用到反射生成bean對(duì)象,其他常見的MVC框架,比如Struts2、SpringMVC等等一些框架里還有很多地方都會(huì)用到反射。

前端夜頁面錄入的一些信息通過表單或者其他形式傳入后端,后端框架就可以利用反射生成對(duì)應(yīng)的對(duì)象,并利用反射操作它的set、get方法把前端傳來的信息封裝到對(duì)象里。

2、框架的代碼里經(jīng)常需要利用反射來操作對(duì)象的set、get方法,來把程序的數(shù)據(jù)封裝到Java對(duì)象中去。

如果每次都使用反射來操作對(duì)象的set、get方法進(jìn)行設(shè)置值和取值的話,過于麻煩,所以JDK里提供了一套API,專門用于操作Java對(duì)象的屬性(set/get方法),這就是內(nèi)省

3、平常用到的框架,除了配置文件的形式,現(xiàn)在很多都使用了注解的形式。

其實(shí)注解也和反射息息相關(guān):使用反射也能輕而易舉的拿到類、字段、方法上的注解,然后編寫注解解析器對(duì)這些注解進(jìn)行解析,做一些相關(guān)的處理


點(diǎn)個(gè)在看你最好看


總結(jié):

1.反射擁有以下四大功能:

  • 在運(yùn)行時(shí)(動(dòng)態(tài)編譯)獲知任意一個(gè)對(duì)象所屬的類。

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

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

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

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

反射的具體原理:

在通常情況下,一定是先有類然后再 new 一個(gè)對(duì)象出來的對(duì)吧,類的正常加載過程是這樣的:

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

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

2.優(yōu)點(diǎn):比較靈活,能夠在運(yùn)行時(shí)動(dòng)態(tài)獲取類的實(shí)例。

缺點(diǎn)

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

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

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

  • 動(dòng)態(tài)代理機(jī)制

  • 使用 JDBC 連接數(shù)據(jù)庫

  • Spring / Hibernate 框架(實(shí)際上是因?yàn)槭褂昧藙?dòng)態(tài)代理,所以才和反射機(jī)制有關(guān),這個(gè)地方可以酌情擴(kuò)展)

3.通過反射我們可以獲取到私有方法里面的屬性,也可以通過反射擴(kuò)展或者節(jié)省我們的代碼,讓我們的代碼更加優(yōu)雅!

4.通過反射,也就是調(diào)用了 getClass() 方法后,我們就獲得了這個(gè)類類對(duì)應(yīng)的 Class 對(duì)象,看到了這個(gè)類的結(jié)構(gòu),輸出了類對(duì)象所屬的類的完整名稱,即找到了對(duì)象的出處。

5.知道了動(dòng)態(tài)加載類,通過反射實(shí)現(xiàn)了*動(dòng)態(tài)加載類



Java的十五篇:反射的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
诸城市| 鄯善县| 锡林郭勒盟| 昆明市| 北辰区| 兰坪| 宁明县| 泊头市| 嘉峪关市| 桃源县| 依兰县| 南投县| 河池市| 长兴县| 乌什县| 民权县| 五常市| 罗田县| 克什克腾旗| 开鲁县| 弥渡县| 丹凤县| 新巴尔虎右旗| 田林县| 马山县| 西乡县| 鄂托克前旗| 玉田县| 宜黄县| 凯里市| 临汾市| 灵川县| 景宁| 桦川县| 东莞市| 盐山县| 米泉市| 图们市| 固阳县| 古田县| 临湘市|