一、兩個(gè)概念
- 編譯型語言:程序在執(zhí)行之前需要將源代碼編譯成機(jī)器語言,再由機(jī)器運(yùn)行機(jī)器碼(二進(jìn)制)褪那。像C/C++、Delphi等都是屬于編譯型語言式塌,編譯型語言需要依賴編譯器博敬,通過編譯器將源代碼編譯成與運(yùn)行平臺(tái)對(duì)應(yīng)的機(jī)器碼,運(yùn)行時(shí)不需要重新解釋運(yùn)行峰尝,所以程序執(zhí)行效率高偏窝,但因?yàn)榫幾g器編譯的機(jī)器碼與運(yùn)行平臺(tái)相關(guān),所以跨平臺(tái)性差武学。
- 解釋性語言:相對(duì)編譯型語言祭往,不需要預(yù)先編譯,以文本方式存儲(chǔ)程序代碼火窒,在運(yùn)行程序時(shí)硼补,必須先由解釋器解釋再運(yùn)行,每執(zhí)行一次就要翻譯一次熏矿,效率較低括勺,像JavaScript,VBScript,Python,Ruby等都是解釋型語言。
二曲掰、JAVA屬于哪種語言?
- 對(duì)于Java語言奈辰,Java程序首先通過編譯器編譯成.class文件栏妖,也就是字節(jié)碼,并非是可以直接由機(jī)器直接運(yùn)行的本地機(jī)器碼,如果在windows平臺(tái)上運(yùn)行奖恰,則字節(jié)碼通過windows平臺(tái)上的JVM(JAVA虛擬機(jī))進(jìn)行解釋執(zhí)行吊趾。如果運(yùn)行在linux平臺(tái)上宛裕,字節(jié)碼則通過linux平臺(tái)上的java虛擬機(jī)進(jìn)行解釋執(zhí)行。所以JAVA語言的跨平臺(tái)特性是通過JVM的跨平臺(tái)特性實(shí)現(xiàn)的论泛,如果沒有JVM揩尸,則不能進(jìn)行跨平臺(tái)。所以JAVA語言兼具解釋性語言和編譯型語言的特性屁奏,可以說是一種“半編譯半解釋”執(zhí)行的語言岩榆。綜合以上特性,編譯型語言和解釋型語言的分類就不太準(zhǔn)確了坟瓢。
- 可以把JAVA語言劃分到編譯型語言中勇边,因?yàn)榫幾g的本質(zhì)就是把一種相對(duì)高級(jí)的語言轉(zhuǎn)換為另一個(gè)相對(duì)低級(jí)的語言,而由.java文件->.class文件的編譯已經(jīng)滿足了這個(gè)特征折联。
三粒褒、為什么要使用反射?
- 我們知道诚镰,JAVA程序需要經(jīng)過編譯器編譯成.class文件奕坟,再由JVM解釋運(yùn)行,這些Class對(duì)象承載了這個(gè)類的所有信息清笨,包括父類月杉、接口、構(gòu)造函數(shù)函筋、方法屬性等沙合,這些.class文件在運(yùn)行之前會(huì)被ClassLoader加載到虛擬機(jī)中,當(dāng)一個(gè)類被虛擬機(jī)加載之后跌帐,JVM就會(huì)在內(nèi)存中自動(dòng)產(chǎn)生一個(gè)Class對(duì)象首懈。我們通過new的形式創(chuàng)建對(duì)象實(shí)際上就是通過這些Class“模板”來創(chuàng)建實(shí)例對(duì)象,而這個(gè)過程對(duì)我們來說是透明的谨敛,具體實(shí)現(xiàn)細(xì)節(jié)我們不得而知究履。
- 從以上分析可知,我們編寫的JAVA類需要經(jīng)過編譯之后再由JVM解釋執(zhí)行脸狸,JVM解釋執(zhí)行的是我們編寫的JAVA類生成的對(duì)應(yīng)的.class文件最仑,因?yàn)槲覀儫o法在程序運(yùn)行的過程中修改.class文件,所以炊甲,我們編寫的JAVA類泥彤,到了程序真正運(yùn)行的時(shí)候是無法修改的,除非我們修改JAVA類卿啡,然后在重新編譯運(yùn)行吟吝。那么我們?cè)趺丛诔绦蜻\(yùn)行的修改類的相關(guān)信息呢?這時(shí)JVM相當(dāng)于給我們提供了一個(gè)接口颈娜,讓我們通過反射來修改已經(jīng)編譯好的JAVA類剑逃。是一種以開發(fā)效率換運(yùn)行效率的手段浙宜。
- 場(chǎng)景:我們需要在程序運(yùn)行的過程中動(dòng)態(tài)生成一個(gè)類的實(shí)例,但是我們?cè)诔绦蜻\(yùn)行之前無法獲知這個(gè)JAVA類的任何信息蛹磺,那也就是說我們?cè)诔绦蛑袩o法定義這個(gè)類粟瞬,也就無法通過new方法來生成相應(yīng)的實(shí)例,也無法再編譯的過程生成相應(yīng)的.class文件萤捆,那我們還能去動(dòng)態(tài)的加載這個(gè)類并為這個(gè)類生成相應(yīng)的實(shí)例嗎裙品?
- 反射的工作原理就是借助和反射相關(guān)的四個(gè)核心類:java.lang.Class:Class類;java.lang.reflect.Constructor:構(gòu)造器類鳖轰;java.lang.reflect.Method:方法類清酥;java.lang.reflect.Field:屬性類。java.lang.Class是一個(gè)Java類蕴侣,繼承自O(shè)bject類焰轻。Class類是一個(gè)java中的泛型類型。Class類是一般類和接口的進(jìn)一步抽象昆雀,而Class類的每個(gè)實(shí)例則代表運(yùn)行中的一個(gè)類辱志。Class類的構(gòu)造函數(shù)是私有的,無法通過new關(guān)鍵字來創(chuàng)建Class類的實(shí)例狞膘。只能通過JVM來調(diào)用它來構(gòu)造一個(gè)Class實(shí)例揩懒。在程序運(yùn)行時(shí)動(dòng)態(tài)訪問和修改任何類的行為和狀態(tài)。
四挽封、反射能做什么已球?
- 通過反射我們可以得到一個(gè)類的所有信息,如訪問一個(gè)類中的所有屬性和方法辅愿,包括訪問權(quán)限受限制的屬性和方法(聲明為private或protected的屬性和方法)智亮,通過反射我們可以訪問一個(gè)對(duì)象的任意一個(gè)屬性和方法。換句話說点待,JVM可以加載一個(gè)運(yùn)行時(shí)才得知名稱的.class文件阔蛉,然后獲悉其完整結(jié)構(gòu),并生成其對(duì)象實(shí)體癞埠、或?qū)ζ鋐ields(變量)設(shè)置状原,或調(diào)用其methods(方法)。
- 反射有如下作用:①操作因訪問權(quán)限限制的屬性和方法苗踪;②實(shí)現(xiàn)自定義注解颠区,如依賴注入(DI),注解處理,動(dòng)態(tài)代理通铲,單元測(cè)試等功能瓦呼。比如Retrofit,Spring,Dagger。;③動(dòng)態(tài)加載第三方j(luò)ar包央串;④按需加載類。⑤實(shí)現(xiàn)序列化與反序列化碗啄,比如PO的ORM,Json解析等质和。⑥實(shí)現(xiàn)跨平臺(tái)兼容,比如JDK中的SocketImpl的實(shí)現(xiàn)稚字。
五饲宿、反射在native中的實(shí)現(xiàn)
- Class.forName的實(shí)現(xiàn)
Class.forName可以通過包名尋找Class對(duì)象,比如Class.forName("java.lang.String")胆描。
在JDK的源碼實(shí)現(xiàn)中瘫想,可以發(fā)現(xiàn)最終調(diào)用的是native方法forName0(),它在JVM中調(diào)用的實(shí)際是findClassFromClassLoader()昌讲,原理與ClassLoader的流程一樣国夜。 - getDeclaredFields的實(shí)現(xiàn)
在JDK源碼中,可以知道class.getDeclaredFields()方法實(shí)際調(diào)用的是native方法getDeclaredFields0()短绸,它在JVM主要實(shí)現(xiàn)步驟如下:
① 根據(jù)Class結(jié)構(gòu)體信息车吹,獲取field_count與fields[]字段,這個(gè)字段早已在load過程中被放入了
② 根據(jù)field_count的大小分配內(nèi)存醋闭、創(chuàng)建數(shù)組
③ 將數(shù)組進(jìn)行forEach循環(huán)窄驹,通過fields[]中的信息依次創(chuàng)建Object對(duì)象
④ 返回?cái)?shù)組指針
主要慢在如下方面:1.創(chuàng)建、計(jì)算证逻、分配數(shù)組對(duì)象乐埠;2.對(duì)字段進(jìn)行循環(huán)賦值。 - Method.invoke的實(shí)現(xiàn)
以下為無同步囚企、無異常的情況下調(diào)用的步驟:
①創(chuàng)建Frame
②如果對(duì)象flag為native丈咐,交給native_handler進(jìn)行處理
③在frame中執(zhí)行java代碼
④彈出Frame
⑤返回執(zhí)行結(jié)果的指針
主要慢在如下方面:1.需要完全執(zhí)行ByteCode而缺少JIT等優(yōu)化;2.檢查參數(shù)非常多洞拨,這些本來可以在編譯器或者加載時(shí)完成扯罐。 - class.newInstance的實(shí)現(xiàn)
①檢測(cè)權(quán)限、預(yù)分配空間大小等參數(shù)
②創(chuàng)建Object對(duì)象烦衣,并分配空間
③通過Method.invoke調(diào)用構(gòu)造函數(shù)(<init>())
④返回Object指針
主要慢在如下方面:1.參數(shù)檢查不能優(yōu)化或者遺漏歹河;2.<init>()的查表;3.Method.invoke本身耗時(shí)花吟。
六秸歧、反射為什么影響性能?
- 前面已經(jīng)說到衅澈,JAVA程序在運(yùn)行之前需要經(jīng)過編譯键菱,然后通過ClassLoader加載到JVM中,而類加載分為:加載->驗(yàn)證->準(zhǔn)備->解析->初始化五個(gè)階段今布,這些都是在運(yùn)行期之前完成的经备,反射慢就慢在把裝載期做的事情搬到了運(yùn)行期拭抬,也就是說在使用反射時(shí),需要在運(yùn)行程序之前把類的加載過程執(zhí)行一遍侵蒙。(解釋正確與否不得而知)另一種說法是:編譯器沒法對(duì)反射相關(guān)的代碼做優(yōu)化造虎。在Stackoverflow上認(rèn)為反射比較慢的程序員主要有如下看法:1.驗(yàn)證等防御代碼過于繁瑣,這一步本來在link階段纷闺,現(xiàn)在卻在計(jì)算時(shí)進(jìn)行驗(yàn)證算凿;2.產(chǎn)生很多臨時(shí)對(duì)象,造成GC與計(jì)算時(shí)間消耗犁功;3.由于缺少上下文氓轰,丟失了很多運(yùn)行時(shí)的優(yōu)化,比如JIT(它可以看作JVM的重要評(píng)測(cè)標(biāo)準(zhǔn)之一).