Java基礎(chǔ)知識(shí)總結(jié)(下)

Java語(yǔ)言的特點(diǎn),與c++的區(qū)別

(1)Java源碼會(huì)先經(jīng)過編譯器編譯成字節(jié)碼(class文件)目胡,然后由JVM中內(nèi)置的解釋器解釋成機(jī)器碼委可。而C++經(jīng)過一次編譯就形成機(jī)器碼C++比Java執(zhí)行效率快挤聘,但是Java可以利用JVM跨平臺(tái)(一次編譯,到處運(yùn)行M背埂)
(2)Java是純面向?qū)ο蟮恼Z(yǔ)言组去,所有代碼都必須在類定義。而C++中還有面向過程的東西步淹,比如全局變量和全局函數(shù)从隆。
(3)C++中有指針,Java中不提供指針直接訪問內(nèi)存缭裆,內(nèi)存更加安全键闺,但是有引用。
(4)C++支持多繼承澈驼,Java類都是單繼承辛燥。但是繼承都有傳遞性,同時(shí)Java中的接口是多繼承,接口可以多實(shí)現(xiàn)挎塌。
(5)Java 中內(nèi)存的分配和回收由Java虛擬機(jī)實(shí)現(xiàn)(自動(dòng)內(nèi)存管理機(jī)制)徘六,會(huì)自動(dòng)清理引用數(shù)為0的對(duì)象。而在 C++ 編程時(shí)榴都,則需要花精力考慮如何避免內(nèi)存泄漏待锈。
(6)C++運(yùn)算符可以重載,但是Java中不可以缭贡。同時(shí)C++中支持強(qiáng)制自動(dòng)轉(zhuǎn)型炉擅,Java中不行,會(huì)出現(xiàn)ClassCastException(類型不匹配)阳惹。

ps:在 C 語(yǔ)?中谍失,字符串或字符數(shù)組最后都會(huì)有?個(gè)額外的字符‘\0’來(lái)表示結(jié)束。但是莹汤,Java 語(yǔ)?中沒有結(jié)束符這?概念快鱼。 這是?個(gè)值得深度思考的問題,具體原因推薦看這篇?章:https://blog.csdn.net/sszgg2006/article/details/49148189

總結(jié):

  • java編譯形成字節(jié)碼纲岭,平臺(tái)無(wú)關(guān)性(擴(kuò)展性好)抹竹,c++一次編譯形成機(jī)器碼(效率高);
  • c++是指針止潮,java是對(duì)象的引用窃判;
  • c++多繼承,java單繼承喇闸,多實(shí)現(xiàn)袄琳;
  • c++需要自己花費(fèi)時(shí)間分配內(nèi)存和垃圾回收,避免內(nèi)存泄露燃乍,java由jvm實(shí)現(xiàn)自動(dòng)內(nèi)存管理(分配和回收)唆樊。**

ps:Java平臺(tái)無(wú)關(guān)性體現(xiàn)在兩個(gè)方面:

JVM: Java 編譯器可生成與計(jì)算機(jī)體系結(jié)構(gòu)無(wú)關(guān)的字節(jié)碼指令,字節(jié)碼文件不僅可以輕易地在任何機(jī)器上解釋執(zhí)行刻蟹,還可以動(dòng)態(tài)地轉(zhuǎn)換成本地機(jī)器代碼逗旁,轉(zhuǎn)換是由 JVM 實(shí)現(xiàn)的,JVM 是平臺(tái)相關(guān)的舆瘪,屏蔽了不同操作系統(tǒng)的差異片效。

語(yǔ)言規(guī)范: 基本數(shù)據(jù)類型大小有明確規(guī)定,例如 int 永遠(yuǎn)為 32 位英古,而 C/C++ 中可能是 16 位淀衣、32 位,也可能是編譯器開發(fā)商指定的其他大小哺呜。Java 中數(shù)值類型有固定字節(jié)數(shù)舌缤,二進(jìn)制數(shù)據(jù)以固定格式存儲(chǔ)和傳輸,字符串采用標(biāo)準(zhǔn)的 Unicode 格式存儲(chǔ)某残。

JDK JRE JVM

  • jdk:開發(fā)者工具(針對(duì)開發(fā)人員)国撵,它是功能齊全的 Java SDK。它擁有 JRE 所擁有的一切玻墅,還有編譯器(javac)和工具(如 javadoc 和 jdb)介牙。它能夠創(chuàng)建和編譯程序。

  • jre:運(yùn)行時(shí)環(huán)境(需要運(yùn)行java程序的人員)澳厢,它是運(yùn)行已編譯 Java 程序所需的所有內(nèi)容的集合环础,包括 Java 虛擬機(jī)(JVM),Java 類庫(kù)剩拢,java 命令和其他的一些基礎(chǔ)構(gòu)件线得。但是,它不能用于創(chuàng)建新程序徐伐。

  • jvm:運(yùn)行java字節(jié)碼的虛擬機(jī)贯钩,JVM 有針對(duì)不同系統(tǒng)的特定實(shí)現(xiàn)(Windows,Linux办素,macOS)角雷,目的是使用相同的字節(jié)碼,它們都會(huì)給出相同的結(jié)果性穿。字節(jié)碼和不同系統(tǒng)的 JVM 實(shí)現(xiàn)是 Java 語(yǔ)言“一次編譯勺三,隨處可以運(yùn)行”的關(guān)鍵所在!P柙吗坚!

三者關(guān)系

什么是字節(jié)碼?采用字節(jié)碼的好處是什么胯舷?

  • 編譯型語(yǔ)言:程序在執(zhí)行之前需要一個(gè)專門的編譯過程刻蚯,把程序編譯成 為機(jī)器語(yǔ)言的文件,運(yùn)行時(shí)不需要重新翻譯桑嘶,直接使用編譯的結(jié)果就行了炊汹。程序執(zhí)行效率高,依賴編譯器逃顶,跨平臺(tái)性差些讨便。如C、C++以政、Delphi等霸褒。
  • 解釋型語(yǔ)言:程序不需要編譯,程序在運(yùn)行時(shí)才翻譯成機(jī)器語(yǔ)言盈蛮,每執(zhí)行一次都要翻譯一次废菱。程序執(zhí)行效率比較低,依賴解釋器,跨平臺(tái)性好殊轴。如Python/JavaScript / Perl /Shell等衰倦。

java中的編譯器和解釋器:

  • Java虛擬機(jī)是在機(jī)器和編譯程序之間加入了一層抽象的虛擬的機(jī)器。這臺(tái)虛擬的機(jī)器在任何平臺(tái)上都提供給編譯程序一個(gè)的共同的接口旁理。編譯程序只需要面向虛擬機(jī)樊零,生成虛擬機(jī)能夠理解的代碼,然后由解釋器來(lái)將虛擬機(jī)代碼轉(zhuǎn)換為特定系統(tǒng)的機(jī)器碼執(zhí)行孽文。在Java中驻襟,這種供虛擬機(jī)理解的代碼叫做字節(jié)碼(即擴(kuò)展名為 .class的文件),它不面向任何特定的處理器芋哭,只面向虛擬機(jī)沉衣。
  • 每一種平臺(tái)的解釋器是不同的,但是實(shí)現(xiàn)的虛擬機(jī)是相同的减牺。Java源程序經(jīng)過編譯器編譯后變成字節(jié)碼厢蒜,字節(jié)碼由虛擬機(jī)解釋執(zhí)行,虛擬機(jī)將每一條要執(zhí)行的字節(jié)碼送給解釋器烹植,解釋器將其翻譯成特定機(jī)器上的機(jī)器碼斑鸦,然后在特定的機(jī)器上運(yùn)行。
  • Java對(duì)不同的操作系統(tǒng)有不同的JVM草雕,Java編譯器可以生成與平臺(tái)無(wú)關(guān)的字節(jié)碼指令巷屿,字節(jié)碼文件不僅可以輕易地在任何機(jī)器上解釋執(zhí)行,還可以動(dòng)態(tài)地轉(zhuǎn)換成本地機(jī)器代碼墩虹,轉(zhuǎn)換是由 JVM 實(shí)現(xiàn)的嘱巾,所以 Java實(shí)現(xiàn)了真正意義上的跨平臺(tái)

注意:字節(jié)碼到機(jī)器碼這一步诫钓,JVM 類加載器首先加載字節(jié)碼文件供炼,然后通過解釋器逐行解釋執(zhí)行勉痴,這種方式的執(zhí)行速度會(huì)相對(duì)比較慢吨娜。而且巷折,有些方法和代碼塊是經(jīng)常需要被調(diào)用的(也就是所謂的熱點(diǎn)代碼),所以后面引進(jìn)了 JIT 編譯器惧所,而 JIT 屬于運(yùn)行時(shí)編譯骤坐。當(dāng) JIT 編譯器完成第一次編譯后,其會(huì)將運(yùn)行頻率高的字節(jié)碼對(duì)應(yīng)的機(jī)器碼保存下來(lái)下愈,下次可以直接使用纽绍。而我們知道,機(jī)器碼的運(yùn)行效率肯定是高于 Java 解釋器的势似。這也解釋了我們?yōu)槭裁唇?jīng)常會(huì)說 Java 是編譯與解釋共存的語(yǔ)言拌夏。

采用字節(jié)碼好處

  • Java語(yǔ)言通過字節(jié)碼的方式僧著,在一定程度上解決了傳統(tǒng)解釋型語(yǔ)言執(zhí)行效率低的問題,同時(shí)又保留了解釋型語(yǔ)言可移植的特點(diǎn)障簿。
  • 由于字節(jié)碼并不專對(duì)一種特定的機(jī)器霹抛,因此,Java程序無(wú)須重新編譯便可在多種不同的計(jì)算機(jī)上運(yùn)行卷谈。

ps:HotSpot 采?了惰性評(píng)估(Lazy Evaluation)的做法,根據(jù)??定律霞篡,消耗?部分系統(tǒng)資源的只有那??部分的代碼(熱點(diǎn)代碼)世蔗,?這也就是 JIT 所需要編譯的部分。JVM 會(huì)根據(jù)代碼每次被執(zhí)?的情況收集信息并相應(yīng)地做出?些優(yōu)化朗兵,因此執(zhí)?的次數(shù)越多污淋,它的速度就越快。JDK 9 引?了?種新的編譯模式AOT(Ahead of Time Compilation)余掖,它是直接將字節(jié)碼編譯成機(jī)器碼寸爆,這樣就避免了 JIT 預(yù)熱等各??的開銷。JDK ?持分層編譯和 AOT 協(xié)作使?盐欺。但是 赁豆,AOT 編譯器的編譯質(zhì)量是肯定?不上 JIT 編譯器的。

創(chuàng)建對(duì)象的方式有哪些冗美?

  • 通過 new 關(guān)鍵字:這是最常用的一種方式魔种,通過 new 關(guān)鍵字調(diào)用類的有參或無(wú)參構(gòu)造方法來(lái)創(chuàng)建對(duì)象。比如 Object obj = new Object();
  • 通過 Class 類的 newInstance() 方法:這種默認(rèn)是調(diào)用類的無(wú)參構(gòu)造方法創(chuàng)建對(duì)象粉洼。比如 Person p2 = (Person) Class.forName("com.ys.test.Person").newInstance();
  • 通過 Constructor 類的 newInstance 方法:這和第二種方法類時(shí)节预,都是通過反射來(lái)創(chuàng)建對(duì)象。通過 java.lang.relect.Constructor 類的 newInstance() 方法指定某個(gè)構(gòu)造器來(lái)創(chuàng)建對(duì)象属韧。Person p3 = (Person) Person.class.getConstructors()[0].newInstance();
  • 利用 Clone 方法:Clone 是 Object 類中的一個(gè)方法安拟,無(wú)論何時(shí)我們調(diào)用一個(gè)對(duì)象的clone方法,JVM就會(huì)創(chuàng)建一個(gè)新的對(duì)象宵喂,將前面的對(duì)象的內(nèi)容全部拷貝進(jìn)去糠赦。通過 對(duì)象A.clone() 方法會(huì)創(chuàng)建一個(gè)內(nèi)容和對(duì)象 A 一模一樣的對(duì)象 B,clone 克隆锅棕,顧名思義就是創(chuàng)建一個(gè)一模一樣的對(duì)象出來(lái)愉棱。Person p4 = (Person) p3.clone();
  • 反序列化: 當(dāng)我們序列化和反序列化一個(gè)對(duì)象,JVM會(huì)給我們創(chuàng)建一個(gè)單獨(dú)的對(duì)象哲戚。序列化是把堆內(nèi)存中的 Java 對(duì)象數(shù)據(jù)奔滑,通過某種方式把對(duì)象存儲(chǔ)到磁盤文件中或者傳遞給其他網(wǎng)絡(luò)節(jié)點(diǎn)(在網(wǎng)絡(luò)上傳輸)。而反序列化則是把磁盤文件中的對(duì)象數(shù)據(jù)或者把網(wǎng)絡(luò)節(jié)點(diǎn)上的對(duì)象數(shù)據(jù)顺少,恢復(fù)成Java對(duì)象模型的過程朋其。

總結(jié):前三種是通過構(gòu)造函數(shù)創(chuàng)建對(duì)象王浴,后兩個(gè)不需要。

深拷貝or淺拷貝梅猿?

拷貝的引入

  • 引用拷貝:創(chuàng)建一個(gè)指向?qū)ο蟮?strong>引用變量的拷貝氓辣,即創(chuàng)建一個(gè)指向該對(duì)象的新的引用變量,teacher與otherteacher指向內(nèi)存地址相同(只是引用不同)袱蚓,所以肯定指向一個(gè)對(duì)象Teacher("Taylor",26)钞啸。
Teacher teacher = new Teacher("Taylor",26);
Teacher otherteacher = teacher;
System.out.println(teacher);
System.out.println(otherteacher);
// -----------結(jié)果-----------------
blog.Teacher@355da254
blog.Teacher@355da254
  • 對(duì)象拷貝創(chuàng)建對(duì)象本身的副本,輸出內(nèi)存地址不同喇潘,即創(chuàng)建了新的對(duì)象(不是把原對(duì)象的地址賦給一個(gè)新的引用變量)体斩。
Teacher teacher = new Teacher("Swift",26);
Teacher otherteacher = (Teacher)teacher.clone();
System.out.println(teacher);
System.out.println(otherteacher);
// -----------結(jié)果-----------------
blog.Teacher@355da254
blog.Teacher@4dc63996

注意:深拷貝和淺拷貝都是對(duì)象拷貝,都是針對(duì)一個(gè)已有的對(duì)象颖低!對(duì)于基本數(shù)據(jù)類型(元類型)絮吵,兩種拷貝方式都是對(duì)值字段的復(fù)制(值傳遞),兩者沒有區(qū)別忱屑。

  • 淺拷貝:被復(fù)制對(duì)象的所有變量都含有與原來(lái)的對(duì)象相同的值蹬敲,而所有的對(duì)其他對(duì)象的引用仍然指向原來(lái)的對(duì)象。即對(duì)象的淺拷貝會(huì)對(duì)“主”對(duì)象進(jìn)行拷貝莺戒,但不會(huì)復(fù)制主對(duì)象里面的對(duì)象伴嗡。"里面的對(duì)象“會(huì)在原來(lái)的對(duì)象和它的副本之間共享。簡(jiǎn)言之从铲,淺拷貝僅僅復(fù)制所考慮的對(duì)象闹究,但不復(fù)制它所引用的對(duì)象(單層的拷貝),故原始對(duì)象及其副本引用的同一個(gè)對(duì)象食店。
  • 深拷貝:深拷貝是一個(gè)整個(gè)獨(dú)立的對(duì)象拷貝渣淤,深拷貝會(huì)拷貝所有的屬性,并拷貝屬性指向的動(dòng)態(tài)分配的內(nèi)存吉嫩。當(dāng)對(duì)象和它所引用的對(duì)象一起拷貝時(shí)即發(fā)生深拷貝价认。深拷貝相比于淺拷貝速度較慢并且花銷較大。簡(jiǎn)言之自娩,深拷貝把要復(fù)制的對(duì)象所引用的對(duì)象都復(fù)制了一遍(多層的拷貝)用踩,修改其中一個(gè)對(duì)象的任何內(nèi)容,不會(huì)影響另一個(gè)對(duì)象的內(nèi)容忙迁。

淺拷貝實(shí)現(xiàn)方式:

  • 首先讓定義的實(shí)體類實(shí)現(xiàn)Cloneable接口脐彩。然后重寫clone方法,將clone方法的修飾符由protected改為public姊扔。這樣就可以通過調(diào)用clone方法進(jìn)行淺拷貝惠奸。

深拷貝實(shí)現(xiàn)方式:

  • 首先是將引用的實(shí)體類也實(shí)現(xiàn)Cloneable接口(同時(shí)重寫clone方法,也是修改修飾符為public)恰梢。然后同樣是讓定義的實(shí)體類實(shí)現(xiàn)Cloneable接口佛南。然后重寫clone方法梗掰,將clone方法的修飾符由protected改為public。但是方法體需要進(jìn)行重寫嗅回,將引用的對(duì)象屬性調(diào)用它本身的clone方法進(jìn)行賦值及穗,然后將賦值后的對(duì)象返回即可。

什么是反射(reflection)機(jī)制绵载?應(yīng)用場(chǎng)景與優(yōu)缺點(diǎn)埂陆。

反射是框架設(shè)計(jì)的靈魂。使用的前提條件:必須先得到代表字節(jié)碼的Class類型對(duì)象

官方解釋:反射是在運(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 語(yǔ)言的反射機(jī)制缓窜。

總結(jié):在運(yùn)行時(shí)定续,構(gòu)造任意類的對(duì)象,動(dòng)態(tài)的獲取類的信息并調(diào)用類的屬性和方法禾锤。

如圖是類的正常加載過程:反射的原理在于Class對(duì)象私股。熟悉一下加載的時(shí)候:Class對(duì)象的由來(lái)是將class文件讀入內(nèi)存,并為之創(chuàng)建一個(gè)Class對(duì)象恩掷,反射的本質(zhì):得到Class對(duì)象反向獲取信息及動(dòng)態(tài)調(diào)用對(duì)象的屬性或者方法倡鲸。

既然Java反射可以訪問和修改私有成員變量,那封裝成private還有意義么黄娘?

從OOP封裝的角度峭状,可以理解為private只是一種約定(我們要去遵守),是一種設(shè)計(jì)規(guī)范逼争。

反射調(diào)用私有方法和屬性:

  • getDeclaredMethods():調(diào)用私有方法
  • getDeclaredFields():獲取私有變量值
  • getConstructor():得到有參的構(gòu)造函數(shù)

注意:setAccessible(true)优床;獲取訪問權(quán)限!

獲取Class對(duì)象的方式(如何使用反射誓焦?):

  • 通過Object類中的getClass方法:因?yàn)樗蓄惗祭^承Object類胆敞。getClass方法:返回一個(gè)對(duì)象的運(yùn)行時(shí)類,進(jìn)而可以通過Class獲取這個(gè)類中的相關(guān)屬性和方法杂伟;
  • 任何數(shù)據(jù)類型(包括基本數(shù)據(jù)類型)都有一個(gè)“靜態(tài)”的class屬性移层;
  • 通過Class類的靜態(tài)方法:Class.forName(String className)(常用,即通過全限定類名(絕對(duì)路徑/真實(shí)路徑)創(chuàng)建Class對(duì)象)
package reflection;
public class Reflection {
    public static void main(String[] args) {
        //第一種方式獲取Class對(duì)象  
        Student stu1 = new Student();   //通過new方式(構(gòu)造器床架對(duì)象)產(chǎn)生一個(gè)Student對(duì)象赫粥,一個(gè)Class對(duì)象观话。
        Class stuClass = stu1.getClass();   //Object類中的getClass()方法,獲取Class對(duì)象
        System.out.println(stuClass.getName());
        
        //第二種方式獲取Class對(duì)象
        Class stuClass2 = Student.class;
        System.out.println(stuClass == stuClass2); //判斷第一種方式獲取的Class對(duì)象和第二種方式獲取的是否是同一個(gè)
        
        //第三種方式獲取Class對(duì)象
        try {
            Class stuClass3 = Class.forName("reflection.Student");  //注意此字符串必須是真實(shí)路徑越平,包名.類名
            System.out.println(stuClass3 == stuClass2);  //判斷三種方式是否獲取的是同一個(gè)Class對(duì)象
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

注意:在運(yùn)行期間匪燕,一個(gè)類蕾羊,只有一個(gè)Class對(duì)象產(chǎn)生。

三種方式常用第三種帽驯,第一種對(duì)象都有了還要反射干什么龟再。第二種需要導(dǎo)入類的包,依賴太強(qiáng)尼变,不導(dǎo)包就拋編譯錯(cuò)誤利凑。一般都第三種,可以通過傳入一個(gè)字符串(全限定類名)嫌术,也可寫在配置文件中等多種方法哀澈。

使用反射調(diào)用類中的方法,分為三種情況:

  • 調(diào)用靜態(tài)方法
  • 調(diào)用公共方法
  • 調(diào)用私有方法
package com.interview.chapter4;
class MyReflect {
    // 靜態(tài)方法
    public static void staticMd() {
        System.out.println("Static Method");
    }
    // 公共方法
    public void publicMd() {
        System.out.println("Public Method");
    }
    // 私有方法
    private void privateMd() {
        System.out.println("Private Method");
    }
    
    public static void main(String[] args) {
        
        // 反射調(diào)用靜態(tài)方法
        Class myClass = Class.forName("com.interview.chapter4.MyReflect");
        Method method = myClass.getMethod("staticMd");
        method.invoke(myClass);
        
        // 反射調(diào)用公共方法
        Class myClass = Class.forName("com.interview.chapter4.MyReflect");
        // 創(chuàng)建實(shí)例對(duì)象(相當(dāng)于 new )
        Object instance = myClass.newInstance();
        Method method2 = myClass.getMethod("publicMd");
        method2.invoke(instance);
        
        // 反射調(diào)用私有方法
        Class myClass = Class.forName("com.interview.chapter4.MyReflect");
        // 創(chuàng)建實(shí)例對(duì)象(相當(dāng)于 new )
        Object object = myClass.newInstance();
        Method method3 = myClass.getDeclaredMethod("privateMd");
        method3.setAccessible(true);
        method3.invoke(object);
    }
}

反射使用總結(jié):

  • 通過 Class.forName("全限定類名")度气,獲取調(diào)用類的Class對(duì)象割按;
  • 反射獲取類實(shí)例要通過 newInstance(),相當(dāng)于 new 一個(gè)新對(duì)象磷籍;
  • 反射獲取方法要通過 getMethod()适荣,獲取到類方法之后使用 invoke() 對(duì)類方法進(jìn)行調(diào)用(執(zhí)行一個(gè)方法)。如果是類方法為私有方法的話院领,則需要通過 setAccessible(true) 來(lái)修改方法的訪問限制弛矛,并且獲取方法使用getDeclaredMethod()。

反射應(yīng)用場(chǎng)景

  • 編程工具 IDEA 或 Eclipse 等比然,在寫代碼時(shí)會(huì)有代碼(屬性或方法名)提示丈氓,就是因?yàn)槭褂昧朔瓷洌?/li>
  • 很多知名框架都用到反射機(jī)制,通過配置加載不同類强法,在不修改源碼的情況下万俗,注入屬性或者調(diào)用方法。

例如饮怯,Spring 可以通過配置來(lái)加載不同的類该编,調(diào)用不同的方法,代碼如下所示:

<bean id="person" class="com.spring.beans.Person" init-method="initPerson">
    
</bean>

例如硕淑,MyBatis 在 Mapper 使用外部類的 Sql 構(gòu)建查詢時(shí)课竣,代碼如下所示:

@SelectProvider(type = PersonSql.class, method = "getListSql")
List<Person> getList();
class PersonSql {
    public String getListSql() {
        String sql = new SQL() {{
            SELECT("*");
            FROM("person");
        }}.toString();
        return sql;
    }
}
  • 數(shù)據(jù)庫(kù)連接池,也會(huì)使用反射調(diào)用不同類型的數(shù)據(jù)庫(kù)驅(qū)動(dòng)置媳,代碼如下所示:
String url = "jdbc:mysql://127.0.0.1:3306/mydb";
String username = "root";
String password = "root";
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, username, password);
  • Web服務(wù)器中利用反射調(diào)用了Sevlet的服務(wù)方法于樟。
  • 另外,像 Java 中的一大利器 注解 的實(shí)現(xiàn)也用到了反射拇囊。為什么你使用 Spring 的時(shí)候 迂曲,一個(gè)@Component注解就聲明了一個(gè)類為 Spring Bean 呢?為什么你通過一個(gè) @Value注解就讀取到配置文件中的值呢寥袭?究竟是怎么起作用的呢路捧?這些都是因?yàn)槟憧梢?strong>基于反射分析類关霸,然后獲取到類/屬性/方法/方法的參數(shù)上的注解。你獲取到注解之后杰扫,就可以做進(jìn)一步的處理队寇。

優(yōu)缺點(diǎn)

  • 優(yōu)點(diǎn):可以動(dòng)態(tài)執(zhí)行,節(jié)省資源在運(yùn)行期間根據(jù)業(yè)務(wù)功能動(dòng)態(tài)執(zhí)行方法章姓、訪問屬性佳遣,最大限度發(fā)揮了java的靈活性。
  • 缺點(diǎn):對(duì)性能有影響凡伊,這類操作總是慢于直接執(zhí)行java代碼零渐,要先生成Class對(duì)象。

動(dòng)態(tài)代理(設(shè)計(jì)模式)系忙,常見的兩種動(dòng)態(tài)代理的實(shí)現(xiàn)诵盼?

寫在前:靜態(tài)代理:每個(gè)代理類只能為一個(gè)接口服務(wù),這樣會(huì)產(chǎn)生很多代理類银还。普通代理模式风宁,代理類Proxy的Java代碼在JVM運(yùn)行時(shí)就已經(jīng)確定了,也就是靜態(tài)代理在編碼編譯階段就確定了Proxy類的代碼见剩。而動(dòng)態(tài)代理是指在JVM運(yùn)行過程中杀糯,動(dòng)態(tài)的創(chuàng)建一個(gè)類的代理類扫俺,并實(shí)例化代理對(duì)象苍苞。

動(dòng)態(tài)代理:首先它是一個(gè)代理機(jī)制,代理可以看作是對(duì)調(diào)用目標(biāo)的一個(gè)包裝狼纬,這樣我們對(duì)目標(biāo)代碼的調(diào)用不是直接發(fā)生的羹呵,而是通過代理完成,通過代理可以讓調(diào)用者與實(shí)現(xiàn)者之間解耦疗琉。比如進(jìn)行 RPC 調(diào)用冈欢,通過代理,可以提供更加友善的界面盈简;還可以通過代理凑耻,做一個(gè)全局的攔截器。代理是一種常用的設(shè)計(jì)模式柠贤,其目的就是為其他對(duì)象提供一個(gè)代理以控制對(duì)某個(gè)對(duì)象的訪問香浩。解決的問題:直接訪問一個(gè)對(duì)象帶來(lái)的問題。

應(yīng)用場(chǎng)景:

  • 經(jīng)典應(yīng)用有 Spring AOP數(shù)據(jù)查詢臼勉、例如邻吭,依賴注入 @Autowired 和事務(wù)注解 @Transactional 等,都是利用動(dòng)態(tài)代理實(shí)現(xiàn)的宴霸;
  • 封裝一些rpc調(diào)用囱晴;
  • 通過代理實(shí)現(xiàn)一個(gè)全局?jǐn)r截器等膏蚓。

動(dòng)態(tài)代理與反射的關(guān)系:反射可以用來(lái)實(shí)現(xiàn)動(dòng)態(tài)代理,但動(dòng)態(tài)代理還有其他的實(shí)現(xiàn)方式畸写,比如 ASM(一個(gè)短小精悍的字節(jié)碼操作框架)驮瞧、cglib 等。

jdk動(dòng)態(tài)代理:

在java的類庫(kù)中艺糜,java.util.reflect.Proxy類就是其用來(lái)實(shí)現(xiàn)動(dòng)態(tài)代理的頂層類剧董。可以通過Proxy類的靜態(tài)方法Proxy.newProxyInstance()方法動(dòng)態(tài)的創(chuàng)建一個(gè)類的代理類破停,并實(shí)例化翅楼。由它創(chuàng)建的代理類都是Proxy類的子類。

JDK動(dòng)態(tài)代理實(shí)現(xiàn)步驟:

  • 編寫需要被代理的類和接口
  • 編寫代理類真慢,需要實(shí)現(xiàn) InvocationHandler 接口毅臊,重寫 invoke() 方法;
  • 使用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)動(dòng)態(tài)創(chuàng)建代理類對(duì)象黑界,通過代理類對(duì)象調(diào)用業(yè)務(wù)方法管嬉。

注意: JDK Proxy 只能代理實(shí)現(xiàn)接口的類(即使是 extends 繼承類也是不可以代理的)

cglib實(shí)現(xiàn)動(dòng)態(tài)代理:

  • CGLIB是一個(gè)高性能的代碼生成類庫(kù)朗鸠,被Spring廣泛應(yīng)用蚯撩。其底層是通過ASM字節(jié)碼框架生成類的字節(jié)碼,達(dá)到動(dòng)態(tài)創(chuàng)建類的目的烛占。

ps:Spring AOP動(dòng)態(tài)代理的實(shí)現(xiàn)方式有兩種:cglib 和 JDK 原生動(dòng)態(tài)代理胎挎。

cglib動(dòng)態(tài)代理的實(shí)現(xiàn)步驟:

  • 創(chuàng)建被代理的目標(biāo)類。
  • 創(chuàng)建一個(gè)方法攔截器類忆家,并實(shí)現(xiàn)CGLIB的MethodInterceptor接口的intercept()方法犹菇。
  • 通過Enhancer類增強(qiáng)工具,創(chuàng)建目標(biāo)類的代理類芽卿。
  • 利用代理類進(jìn)行方法調(diào)用揭芍,就像調(diào)用真實(shí)的目標(biāo)類方法一樣。

要是用 cglib 實(shí)現(xiàn)要添加對(duì) cglib 的依賴卸例,如果是 maven 項(xiàng)目的話称杨,直接添加以下代碼:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.12</version>
</dependency>

cglib 的具體實(shí)現(xiàn),請(qǐng)參考以下代碼:

class Panda {
    public void eat() {
        System.out.println("The panda is eating");
    }
}
class CglibProxy implements MethodInterceptor {
    private Object target; // 代理對(duì)象
    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        // 設(shè)置父類為實(shí)例類
        enhancer.setSuperclass(this.target.getClass());
        // 回調(diào)方法
        enhancer.setCallback(this);
        // 創(chuàng)建代理對(duì)象
        return enhancer.create();
    }
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("調(diào)用前");
        Object result = methodProxy.invokeSuper(o, objects); // 執(zhí)行方法調(diào)用
        System.out.println("調(diào)用后");
        return result;
    }
}
public static void main(String[] args) {
    // cglib 動(dòng)態(tài)代理調(diào)用
    CglibProxy proxy = new CglibProxy();
    Panda panda = (Panda)proxy.getInstance(new Panda());
    panda.eat();
}

由以上代碼可以知道筷转,cglib 的調(diào)用通過實(shí)現(xiàn) MethodInterceptor 接口的 intercept 方法姑原,調(diào)用 invokeSuper 進(jìn)行動(dòng)態(tài)代理的。它可以直接對(duì)普通類(可以有子類的普通類旦装,但不能代理最終類)進(jìn)行動(dòng)態(tài)代理页衙,并不需要像 JDK 代理那樣,需要通過接口來(lái)完成, Spring 的動(dòng)態(tài)代理也是通過 cglib 實(shí)現(xiàn)的店乐。

注意:cglib 底層是通過子類繼承被代理對(duì)象的方式實(shí)現(xiàn)動(dòng)態(tài)代理的(即艰躺,動(dòng)態(tài)的生成被代理類的子類),因此代理類不能是最終類(final)眨八,否則就會(huì)報(bào)錯(cuò) java.lang.IllegalArgumentException: Cannot subclass final class xxx腺兴。

JDK原生態(tài)動(dòng)態(tài)代理和CGlib區(qū)別

  • JDK 原生動(dòng)態(tài)代理:只能代理實(shí)現(xiàn)接口的類(即使是 extends 繼承類也是不可以代理的),不需要添加任何依賴廉侧,可以平滑的支持 JDK 版本的升級(jí)页响;
  • cglib 不需要實(shí)現(xiàn)接口,底層通過子類繼承被代理對(duì)象的方式實(shí)現(xiàn)動(dòng)態(tài)代理段誊∪虿希可以直接代理普通類(但不能代理final修飾的類),需要添加依賴包连舍,性能更高没陡。

兩者的區(qū)別:

  • JDK 動(dòng)態(tài)代理:基于 Java 反射機(jī)制實(shí)現(xiàn),必須要實(shí)現(xiàn)了接口的業(yè)務(wù)類(extends 繼承類不可代理)才能用這種辦法生成代理對(duì)象索赏。
  • CGLib 動(dòng)態(tài)代理:基于 ASM 機(jī)制實(shí)現(xiàn)盼玄,通過生成業(yè)務(wù)類的子類作為代理類(本質(zhì)是子類繼承被代理類的方法),所以代理的類不能是 final 修飾的潜腻。

JDK Proxy 的優(yōu)勢(shì):

  • 最小化依賴關(guān)系埃儿,減少依賴意味著簡(jiǎn)化開發(fā)和維護(hù),JDK 本身的支持融涣,可能比 CGLib 更加可靠童番。
  • 平滑進(jìn)行 JDK 版本升級(jí),而字節(jié)碼類庫(kù)通常需要進(jìn)行更新以保證在新版 Java 上能夠使用暴心。
  • 代碼實(shí)現(xiàn)簡(jiǎn)單妓盲。

基于類似 CGLib 框架的優(yōu)勢(shì):

  • 無(wú)需實(shí)現(xiàn)接口杂拨,達(dá)到代理類無(wú)侵入专普。故CGLib適合那些沒有接口抽象的類代理。
  • 只操作我們關(guān)心的類弹沽,而不必為其他相關(guān)類增加工作量檀夹。

ps:為什么 JDK 原生的動(dòng)態(tài)代理必須要通過接口來(lái)完成?

這是由于 JDK 原生設(shè)計(jì)的原因策橘,動(dòng)態(tài)代理的實(shí)現(xiàn)方法 newProxyInstance() 的源碼如下:

/**
 * ......
 * @param   loader the class loader to define the proxy class
 * @param   interfaces the list of interfaces for the proxy class to implement
 * ......
 */ 
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
// 省略其他代碼

前兩個(gè)參數(shù)的聲明:

  • loader:為類加載器炸渡,也就是 target.getClass().getClassLoader()
  • interfaces:接口代理類的接口實(shí)現(xiàn)列表

因此,要使用 JDK 原生的動(dòng)態(tài)只能通過實(shí)現(xiàn)接口來(lái)完成丽已。

什么是注解蚌堵,什么是元注解?

注解是一種標(biāo)記,使類或接口附加額外信息吼畏,幫助編譯器和 JVM 完成一些特定功能督赤,例如 @Override 標(biāo)識(shí)一個(gè)方法是重寫方法。

原理:注解的底層也是使用反射實(shí)現(xiàn)的泻蚊。可以發(fā)現(xiàn)注解的本質(zhì)就是接口躲舌,這個(gè)接口繼承了jdk里面的Annotation接口。

元注解是自定義注解的注解性雄,例如:

  • @Target:約束作用位置没卸,值是 ElementType 枚舉常量,包括 METHOD 方法秒旋、VARIABLE 變量约计、TYPE 類/接口、PARAMETER 方法參數(shù)迁筛、CONSTRUCTORS 構(gòu)造方法和 LOACL_VARIABLE 局部變量等病蛉。
  • @Rentention:約束生命周期,值是 RetentionPolicy 枚舉常量瑰煎,包括 SOURCE 源碼铺然、CLASS 字節(jié)碼和 RUNTIME 運(yùn)行時(shí)。
  • @Documented:表明這個(gè)注解應(yīng)該被 javadoc 記錄酒甸。

注解的作用:

  • 生成文檔魄健,常用的有@param@return等。
  • 替代配置文件的作用插勤,尤其是在spring等一些框架中沽瘦,使用注解可以大量的減少配置文件的數(shù)量。比如@Configuration標(biāo)識(shí)這是一個(gè)配置類农尖,@ComponentScan("spring.ioc.stu")配置包掃描路徑 :
@Configuration
@ComponentScan("spring.ioc.stu")
public class SpringConfiguration {
     xxx;
}
  • 檢查代碼的格式析恋,如@Override,標(biāo)識(shí)某一個(gè)方法是否覆蓋了它的父類的方法盛卡。

三大內(nèi)置注解:

  • @Deprecated 已過期助隧,表示方法是不被建議使用的
  • @Override 重寫,標(biāo)識(shí)覆蓋它的父類的方法
  • @SuppressWarnings 壓制警告滑沧,抑制警告

巨人的肩膀:

https://www.cnblogs.com/ysocean/p/8482979.html
http://www.reibang.com/p/35d69cf24f1f
https://baiyexing.blog.csdn.net/article/details/71788741
https://blog.csdn.net/sinat_38259539/article/details/71799078
https://blog.csdn.net/huanglei305/article/details/101012177'kk

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末并村,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子滓技,更是在濱河造成了極大的恐慌哩牍,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件令漂,死亡現(xiàn)場(chǎng)離奇詭異膝昆,居然都是意外死亡丸边,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門荚孵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)原环,“玉大人,你說我怎么就攤上這事处窥≈雎穑” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵滔驾,是天一觀的道長(zhǎng)谒麦。 經(jīng)常有香客問我,道長(zhǎng)哆致,這世上最難降的妖魔是什么绕德? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮摊阀,結(jié)果婚禮上耻蛇,老公的妹妹穿的比我還像新娘。我一直安慰自己胞此,他們只是感情好臣咖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著漱牵,像睡著了一般夺蛇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酣胀,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天刁赦,我揣著相機(jī)與錄音,去河邊找鬼闻镶。 笑死甚脉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的铆农。 我是一名探鬼主播牺氨,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼顿涣!你這毒婦竟也來(lái)了波闹?” 一聲冷哼從身側(cè)響起酝豪,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涛碑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后孵淘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蒲障,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了揉阎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庄撮。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖毙籽,靈堂內(nèi)的尸體忽然破棺而出洞斯,到底是詐尸還是另有隱情,我是刑警寧澤坑赡,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布烙如,位于F島的核電站,受9級(jí)特大地震影響毅否,放射性物質(zhì)發(fā)生泄漏亚铁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一螟加、第九天 我趴在偏房一處隱蔽的房頂上張望徘溢。 院中可真熱鬧,春花似錦捆探、人聲如沸然爆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)施蜜。三九已至,卻和暖如春雌隅,著一層夾襖步出監(jiān)牢的瞬間翻默,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工恰起, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留修械,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓检盼,卻偏偏與公主長(zhǎng)得像肯污,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吨枉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容