點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
作者 | Baret-H
來(lái)源 | urlify.cn/6RVNRr
1. JDK、JRE、JVM的區(qū)別和聯(lián)系

JDK(java程序開(kāi)發(fā)包)=JRE +Tools
JRE=JVM(虛擬機(jī))+API
2. 采用字節(jié)碼的好處
Java中引入了jvm,即在機(jī)器和編譯程序之間加了一層抽象的虛擬機(jī)器,這臺(tái)機(jī)器在任何平臺(tái)上都提供給編譯程序一個(gè)共同的接口。
編譯程序只需要面向虛擬機(jī),生成虛擬機(jī)能夠理解的代碼,然后由解釋器來(lái)將虛擬機(jī)代碼轉(zhuǎn)換為特定系統(tǒng)的機(jī)器碼來(lái)執(zhí)行。在Java中,這種供虛擬機(jī)理解的代碼叫做字節(jié)碼(.class),它不面向任何特定的處理器,只面向虛擬機(jī)
每一種平臺(tái)的解釋器是不同的,但是實(shí)現(xiàn)的虛擬機(jī)是相同的。Java源程序通過(guò)編譯器進(jìn)行編譯后轉(zhuǎn)換為字節(jié)碼,字節(jié)碼在虛擬機(jī)上執(zhí)行,虛擬機(jī)將每一條要執(zhí)行的字節(jié)碼送給解釋器,解釋器將其翻譯成特定機(jī)器上的機(jī)器碼,然后在特定的機(jī)器上運(yùn)行。這也解釋了Java的編譯與解釋共存的特點(diǎn)。
Java源代碼-->編譯器-->jvm可執(zhí)行的java字節(jié)碼-->jvm中解釋器-->機(jī)器可執(zhí)行的二進(jìn)制機(jī)器碼-->程序運(yùn)行
Java語(yǔ)言采用字節(jié)碼的方式,一定程度上解決了傳統(tǒng)解釋型語(yǔ)言執(zhí)行效率低(運(yùn)行需要解釋環(huán)境,速度比編譯的要慢,占用資源也要多一些)的問(wèn)題,同時(shí)又保留了解釋型語(yǔ)言可移植的特點(diǎn),所以Java程序運(yùn)行時(shí)很高效,此外,由于字節(jié)碼不針對(duì)一種特定的機(jī)器,因此Java源程序無(wú)需重新編譯即可在不同的計(jì)算機(jī)上運(yùn)行,實(shí)現(xiàn)一次編譯,多次運(yùn)行
3. 接口和抽象類的區(qū)別
1?? 從語(yǔ)法上來(lái)說(shuō)
抽象類可以存在普通成員函數(shù),而接口中只能存在public abstract方法。
抽象類中的成員變量可以是各種類型的,而接口中的成員變量只能是public static final類型的。
抽象類只能繼承一個(gè),接口可以實(shí)現(xiàn)多個(gè)
2?? 從設(shè)計(jì)目的來(lái)說(shuō)
接口是用來(lái)對(duì)
類的形為進(jìn)行約束。也就是提供了一種機(jī)制,可以強(qiáng)制要求所有的類具有相同的形為,只約束了行為的有無(wú),不限制形為的具體實(shí)現(xiàn)
抽象類是為了
代碼復(fù)用。當(dāng)不同的類具有相同的行為A,且其中一部分形為B的實(shí)現(xiàn)方式一致時(shí),可以讓這些類都派生于一個(gè)抽象類,這個(gè)抽象類中實(shí)現(xiàn)了B,避免讓所有的子類來(lái)實(shí)現(xiàn)B,以此來(lái)達(dá)到代碼復(fù)用的目的。而A-B的部分,交給各個(gè)子類自己實(shí)現(xiàn),正是由于這里A-B的行為沒(méi)有實(shí)現(xiàn),所以抽象類不允許實(shí)例化
3?? 從本質(zhì)上來(lái)說(shuō)
接口是
對(duì)行為的抽象,表達(dá)的是like a的關(guān)系,比如 Bird like a Aircraft(鳥(niǎo)像飛行器一樣可以飛);接口的核心是定義行為,即接口的實(shí)現(xiàn)類可以做什么,至于實(shí)現(xiàn)類如何實(shí)現(xiàn),主體是誰(shuí),接口并不關(guān)心
抽象類是
對(duì)類本質(zhì)的抽象,表達(dá)的是is a的關(guān)系,比如 BaoMa is a Car(寶馬是一輛車);抽象類包含并實(shí)現(xiàn)子類的通用特性,將子類存在差異化的特性進(jìn)行抽象,交給子類去實(shí)現(xiàn)
總結(jié):
當(dāng)你關(guān)注一個(gè)事物的本質(zhì)的時(shí)候,用抽象類;當(dāng)你關(guān)注一個(gè)操作的時(shí)候,用接口。
抽象類的功能要遠(yuǎn)超過(guò)接口,但是,定義抽象類的代價(jià)高。因?yàn)楦呒?jí)語(yǔ)言來(lái)說(shuō)(從實(shí)際設(shè)計(jì)上來(lái)說(shuō)也是)每個(gè)類只能繼承一個(gè)類。在這個(gè)類中,你必須繼承或編寫出其所有子類的所有共性。雖然接口在功能上會(huì)弱化許多,但是它只是針對(duì)一個(gè)動(dòng)作的描述。而且你可以在一個(gè)類中同時(shí)實(shí)現(xiàn)多個(gè)接口。在設(shè)計(jì)階段會(huì)降低難度
4. 面向?qū)ο蟮乃拇筇匦?/span>
1?? 抽象
將一類對(duì)象的共同特征總結(jié)出來(lái)構(gòu)造類的過(guò)程
2?? 封裝
將過(guò)程和數(shù)據(jù)包圍起來(lái),對(duì)數(shù)據(jù)的訪問(wèn)只能通過(guò)特定的接口(例如私有變量的get/set方法)
3?? 繼承
從現(xiàn)有類派生出新類的過(guò)程
4?? 多態(tài)
5. 面向?qū)ο蠛兔嫦蜻^(guò)程
面向過(guò)程(Procedure Oriented)和面向?qū)ο?Object Oriented,OO)都是對(duì)軟件分析、設(shè)計(jì)和開(kāi)發(fā)的一種思想,它指導(dǎo)著人們以不同的方式去分析、設(shè)計(jì)和開(kāi)發(fā)軟件。早期先有面向過(guò)程思想,隨著軟件規(guī)模的擴(kuò)大,問(wèn)題復(fù)雜性的提高,面向過(guò)程的弊端越來(lái)越明顯的顯示出來(lái),出現(xiàn)了面向?qū)ο笏枷氩⒊蔀槟壳爸髁鞯姆绞健烧叨钾灤┯谲浖治觥⒃O(shè)計(jì)和開(kāi)發(fā)各個(gè)階段,對(duì)應(yīng)面向?qū)ο缶头謩e稱為面向?qū)ο蠓治?OOA)、面向?qū)ο笤O(shè)計(jì)(OOD)和面向?qū)ο缶幊?OOP)。C語(yǔ)言是一種典型的面向過(guò)程語(yǔ)言,Java是一種典型的面向?qū)ο笳Z(yǔ)言。
面向?qū)ο蠛兔嫦蜻^(guò)程是兩種不同的處理問(wèn)題角度
比如:洗衣機(jī)洗衣服
面向過(guò)程會(huì)將任務(wù)拆解成一系列的步驟(函數(shù)):
1、打開(kāi)洗衣機(jī)–>2、放衣服–>3、放洗衣粉–>4、清洗–>5、烘干
面向?qū)ο髸?huì)拆出人和洗衣機(jī)兩個(gè)對(duì)象:
人:打開(kāi)洗衣機(jī)放衣服放洗衣粉
洗衣機(jī):清洗烘干
由此可見(jiàn),面向過(guò)程比較直接高效,而面向?qū)ο蟾子趶?fù)用、擴(kuò)展和維護(hù)
面向?qū)ο蠛兔嫦蜻^(guò)程的總結(jié)
都是解決問(wèn)題的思維方式,都是代碼組織的方式。
解決簡(jiǎn)單問(wèn)題可以使用面向過(guò)程
解決復(fù)雜問(wèn)題:宏觀上使用面向?qū)ο蟀盐眨⒂^處理上仍然是面向過(guò)程。
面向?qū)ο缶哂腥筇卣鳎悍庋b性、繼承性和多態(tài)性,而面向過(guò)程沒(méi)有繼承性和多態(tài)性,并且面向過(guò)程的封裝只是封裝功能,而面向?qū)ο罂梢苑庋b數(shù)據(jù)和功能。所以面向?qū)ο髢?yōu)勢(shì)更明顯
6. 靜態(tài)綁定&動(dòng)態(tài)綁定
在Java方法調(diào)用的過(guò)程中,JVM是如何知道調(diào)用的是哪個(gè)類的方法源代碼呢?這就涉及到程序綁定,程序綁定指的是一個(gè)方法的調(diào)用與方法所在的類(方法主體)關(guān)聯(lián)起來(lái)。
對(duì)Java來(lái)說(shuō),綁定分為靜態(tài)綁定和動(dòng)態(tài)綁定,或者叫做前期綁定和后期綁定。
1?? 靜態(tài)綁定
針對(duì)Java,可以簡(jiǎn)單地理解為程序編譯期的綁定。
這里特別說(shuō)明一點(diǎn),Java當(dāng)中的方法只有final,static,private和構(gòu)造方法是靜態(tài)綁定。
# 關(guān)于final,static,private和構(gòu)造方法是前期綁定的理解:
對(duì)于private的方法,首先一點(diǎn)它不能被繼承,既然不能被繼承那么就沒(méi)辦法通過(guò)它子類的對(duì)象來(lái)調(diào)用,而只能通過(guò)這個(gè)類自身的對(duì)象來(lái)調(diào)用。因此就可以說(shuō)private方法和定義這個(gè)方法的類綁定在了一起。
final方法雖然可以被繼承,但不能被重寫(覆蓋),雖然子類對(duì)象可以調(diào)用,但是調(diào)用的都是父類中所定義的那個(gè)final方法,(由此我們可以知道將方法聲明為final類型,一是為了防止方法被覆蓋,二是為了有效地關(guān)閉java中的動(dòng)態(tài)綁定)。
構(gòu)造方法也是不能被繼承的(網(wǎng)上也有說(shuō)子類無(wú)條件地繼承父類的無(wú)參數(shù)構(gòu)造函數(shù)作為自己的構(gòu)造函數(shù),不過(guò)個(gè)人認(rèn)為這個(gè)說(shuō)法不太恰當(dāng),因?yàn)槲覀冎雷宇愂峭ㄟ^(guò)super()來(lái)調(diào)用父類的無(wú)參構(gòu)造方法,來(lái)完成對(duì)父類的初始化, 而我們使用從父類繼承過(guò)來(lái)的方法是不用這樣做的,因此不應(yīng)該說(shuō)子類繼承了父類的構(gòu)造方法),因此編譯時(shí)也可以知道這個(gè)構(gòu)造方法到底是屬于哪個(gè)類。
對(duì)于static方法,具體的原理我也說(shuō)不太清。不過(guò)根據(jù)網(wǎng)上的資料和我自己做的實(shí)驗(yàn)可以得出結(jié)論:static方法可以被子類繼承,但是不能被子類重寫(覆蓋),但是可以被子類隱藏。(這里意思是說(shuō)如果父類里有一個(gè)static方法,它的子類里如果沒(méi)有對(duì)應(yīng)的方法,那么當(dāng)子類對(duì)象調(diào)用這個(gè)方法時(shí)就會(huì)使用父類中的方法。而如果子類中定義了相同的方法,則會(huì)調(diào)用子類的中定義的方法。唯一的不同就是,當(dāng)子類對(duì)象上轉(zhuǎn)型為父類對(duì)象時(shí),不論子類中有沒(méi)有定義這個(gè)靜態(tài)方法,該對(duì)象都會(huì)使用父類中的靜態(tài)方法。因此這里說(shuō)靜態(tài)方法可以被隱藏而不能被覆蓋。這與子類隱藏父類中的成員變量是一樣的。隱藏和覆蓋的區(qū)別在于,子類對(duì)象轉(zhuǎn)換成父類對(duì)象后,能夠訪問(wèn)父類被隱藏的變量和方法,而不能訪問(wèn)父類被覆蓋的方法)
由上面我們可以得出結(jié)論,如果一個(gè)方法不可被繼承或者繼承后不可被覆蓋,那么這個(gè)方法就采用的靜態(tài)綁定。
2?? 動(dòng)態(tài)綁定
在運(yùn)行時(shí)根據(jù)具體對(duì)象的類型進(jìn)行綁定。也就是說(shuō),編譯器此時(shí)依然不知道對(duì)象的類型,但方法調(diào)用機(jī)制能自己去調(diào)查,找到正確的方法主體
動(dòng)態(tài)綁定的過(guò)程:
虛擬機(jī)提取對(duì)象的實(shí)際類型的方法表;
虛擬機(jī)搜索方法簽名
調(diào)用方法
7. 重載和重寫
重寫:發(fā)生在父子類中,方法名、參數(shù)列表必須相同;子類的返回值范圍小于等于父類,拋出異常范圍小于等于父類,訪問(wèn)修飾符范圍大于等于父類;如果父類方法訪問(wèn)修飾符為private則子類不能重寫該方法。
重載:發(fā)生在同一個(gè)類中,參數(shù)類型不同、個(gè)數(shù)不同、順序不同都可以構(gòu)成重載;
- 重載方法的返回值可以不同,但是不能僅僅返回值不同,否則編譯時(shí)報(bào)錯(cuò)

- 重載方法的訪問(wèn)控制符也可以不同,但是不能僅僅訪問(wèn)控制符不同,否則編譯時(shí)報(bào)錯(cuò)

8. Java異常體系

Java中的所有異常都來(lái)自頂級(jí)父類Throwable,Throwable有兩個(gè)子類Exception和Error
Error是程序無(wú)法處理的錯(cuò)誤,一旦出現(xiàn)錯(cuò)誤,則程序?qū)⒈黄韧V惯\(yùn)行
Exception不會(huì)導(dǎo)致程序停止,又分為RunTimeException和和CheckedException
//除0錯(cuò)誤:ArithmeticException
//錯(cuò)誤的強(qiáng)制類型轉(zhuǎn)換錯(cuò)誤:ClassCastException
//數(shù)組索引越界:ArrayIndexOutOfBoundsException
//使用了空對(duì)象:NullPointerException
CheckedException常常發(fā)生在程序編譯過(guò)程中,會(huì)導(dǎo)致程序編譯不通過(guò)
例如:打開(kāi)不存在的文件
9. final關(guān)鍵字
1.作用
修飾類:表示類不可被繼承
修飾方法:表示方法不可被子類覆蓋,但是可以重載
修飾變量:表示變量一旦被賦值就不可以更改它的值
2.修飾不同變量的區(qū)別
1?? 修飾成員變量
2?? 修飾局部變量
系統(tǒng)不會(huì)為局部變量進(jìn)行初始化,局部變量必須由程序員顯示初始化。因此使用final修飾局部變量時(shí),即可以在定義時(shí)指定默認(rèn)值(后面的代碼不能對(duì)變量再賦值),也可以不指定默認(rèn)值,而在后面的代碼中對(duì)final變量賦初值(僅一次)

3?? 修飾基本數(shù)據(jù)類型和引用類型數(shù)據(jù)

2.為什么局部?jī)?nèi)部類和匿名內(nèi)部類只能訪問(wèn)局部final變量
局部?jī)?nèi)部類或匿名內(nèi)部類編譯之后會(huì)產(chǎn)生兩個(gè)class文件:Test.class、Test$1.class,一個(gè)是類class,一個(gè)是內(nèi)部類class

局部?jī)?nèi)部類:

首先需要知道的一點(diǎn)是:內(nèi)部類和外部類是處于同一個(gè)級(jí)別的,內(nèi)部類不會(huì)因?yàn)槎x在方法中就會(huì)隨著方法的執(zhí)行完畢就被銷毀。
這里就會(huì)產(chǎn)生問(wèn)題:當(dāng)外部類的方法結(jié)束時(shí),局部變量就會(huì)被銷毀了,但是內(nèi)部類對(duì)象可能還存在(只有沒(méi)有人再引用它時(shí),才會(huì)死亡),這里就出現(xiàn)了一個(gè)矛盾:內(nèi)部類對(duì)象訪問(wèn)了一個(gè)不存在的變量。為了解決這個(gè)問(wèn)題,就將局部變量復(fù)制了一份作為內(nèi)部類的成員變量,這樣當(dāng)局部變量死亡后,內(nèi)部類仍可以訪問(wèn)它,實(shí)際訪問(wèn)的是局部變量的"copy".這樣就好像延長(zhǎng)了局部變量的生命周期
將局部變量復(fù)制為內(nèi)部類的成員變量時(shí),必須保證這兩個(gè)變量是一樣的,也就是如果我們?cè)趦?nèi)部類中修改了成員變量,方法中的局部變量也得跟著改變,怎么解決問(wèn)題呢?
就將局部變量設(shè)置為final,對(duì)它初始化后,我就不讓你再去修改這個(gè)變量,就保證了內(nèi)部類的成員變量和方法的局部變量的一致性。這實(shí)際上也是一種妥協(xié)。使得局部變量與內(nèi)部類內(nèi)建立的拷貝保持一致。
10. String、StringBuilder、StringBuffer
String底層是final修飾的char[]數(shù)組,不可變,每次操作都會(huì)產(chǎn)生新的String對(duì)象
StringBuffer和StringBuilder都是在原對(duì)象上操作
StringBuffer線程安全(所有方法都用synchronized修飾),StringBuilder線程不安全
性能:StringBuilder>StringBuffer>String
使用場(chǎng)景:經(jīng)常需要改變字符串內(nèi)容時(shí)使用后面兩個(gè),優(yōu)先使用 StringBuilder,多線程使用共享變量時(shí)使用 StringBuffer
11. 單例模式
徹底玩轉(zhuǎn)單例模式
12. 工廠模式和建造者模式的區(qū)別
工廠模式一般都是創(chuàng)建一個(gè)產(chǎn)品,注重的是把這個(gè)產(chǎn)品創(chuàng)建出來(lái),而不關(guān)心這個(gè)產(chǎn)品的組成部分。從代碼上看,工廠模式就是一個(gè)方法,用這個(gè)方法來(lái)生產(chǎn)出產(chǎn)品
建造者模式也是創(chuàng)建一個(gè)產(chǎn)品,但是不僅要把這個(gè)產(chǎn)品創(chuàng)建出來(lái),還要關(guān)心這個(gè)產(chǎn)品的組成細(xì)節(jié),組成過(guò)程。從代碼上看,建造者模式在創(chuàng)建產(chǎn)品的時(shí)候,這個(gè)產(chǎn)品有很多方法,建造者模式會(huì)根據(jù)這些相同的方法按不同的執(zhí)行順序建造出不同組成細(xì)節(jié)的產(chǎn)品
13. 深拷貝和淺拷貝
淺拷貝:復(fù)制對(duì)象時(shí)只復(fù)制對(duì)象本身,包括基本數(shù)據(jù)類型的屬性,但是不會(huì)復(fù)制引用數(shù)據(jù)類型屬性指向的對(duì)象,即拷貝對(duì)象的與原對(duì)象的引用數(shù)據(jù)類型的屬性指向同一個(gè)對(duì)象
淺拷貝沒(méi)有達(dá)到完全復(fù)制,即原對(duì)象與克隆對(duì)象之間有關(guān)系,會(huì)相互影響
深拷貝:復(fù)制一個(gè)新的對(duì)象,引用數(shù)據(jù)類型指向?qū)ο髸?huì)拷貝新的一份,不再指向原有引用對(duì)象的地址
深拷貝達(dá)到了完全復(fù)制的目的,即原對(duì)象與克隆對(duì)象之間不會(huì)相互影響
14. 泛型知識(shí)
Java泛型深度解析以及面試題_周將的博客-CSDN博客
Java泛型是在JDK5引入的新特性,它提供了編譯時(shí)類型安全檢測(cè)機(jī)制。該機(jī)制允許程序員在編譯時(shí)檢測(cè)到非法的類型,泛型的本質(zhì)是參數(shù)類型。
1?? 使用泛型的好處
泛型可以增強(qiáng)編譯時(shí)錯(cuò)誤檢測(cè),減少因類型問(wèn)題引發(fā)的運(yùn)行時(shí)異常。
泛型可以避免類型轉(zhuǎn)換。
泛型可以泛型算法,增加代碼復(fù)用性。
2?? Java中泛型的分類
泛型類:它的定義格式是class name<T1, T2, ..., Tn>,如下, 返回一個(gè)對(duì)象中包含了code和一個(gè)data, data是一個(gè)對(duì)象,我們不能固定它是什么類型,這時(shí)候就用T泛型來(lái)代替,大大增加了代碼的復(fù)用性。
public class Result<T> {
private T data;
private int code;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
泛型接口:和泛型類使用相似
泛型方法:它的定義是[public] [static] <T> 返回值類型 方法名(T 參數(shù)列表),只有在前面加<T>這種的才能算是泛型方法,比如上面的setData方法雖然有泛型,但是不能算泛型方法
3?? 常見(jiàn)的泛型參數(shù)
K 鍵
V 值
N 數(shù)字
T 類型
E 元素
S, U, V 等,泛型聲明的多個(gè)類型
4?? 鉆石運(yùn)算符Diamond
鉆石操作符是在 java 7 中引入的,可以讓代碼更易讀,但它不能用于匿名的內(nèi)部類。在 java 9 中, 它可以與匿名的內(nèi)部類一起使用,從而提高代碼的可讀性。
5?? 受限類型參數(shù)
它的作用是對(duì)泛型變量的范圍作出限制,格式:
單一限制:<U extends Number>
多種限制:<U extends A & B & C>
多種限制的時(shí)候,類必須寫在第一個(gè)
6?? 通配符
通配符用?標(biāo)識(shí),分為受限制的通配符和不受限制的通配符,它使代碼更加靈活,廣泛運(yùn)用于框架中。
比如List<Number>和List<Integer>是沒(méi)有任何關(guān)系的。如果我們將print方法中參數(shù)列表部分的List聲明為List<Number> list, 那么編譯是不會(huì)通過(guò)的,但是如果我們將List定義為List<? extends Number> list或者List<?> list,那么在編譯的時(shí)候就不會(huì)報(bào)錯(cuò)了


受限制的通配符:語(yǔ)法為<? extends XXX>,它可以擴(kuò)大兼容的范圍(XXX以及它的子類)
比如上面例子中print中如果改為L(zhǎng)ist<Number>,雖然它能存儲(chǔ)Integer和Double等類型的元素,但是作為參數(shù)傳遞的時(shí)候,它只能接受List<Number>這一種類型。如果聲明為L(zhǎng)ist<? extends Number> list就不一樣了,相當(dāng)于擴(kuò)大了類型的范圍,使得代碼更加的靈活,代碼復(fù)用性更高。
<? super T>和extends一樣,只不過(guò)extends是限定了上限,而super是限定了下限
非受限制的通配符:不適用關(guān)鍵字extends或者super。比如上面print參數(shù)列表聲明為L(zhǎng)ist<?> list也可以解決問(wèn)題。?代表了未知類型。所以所有的類型都可以理解為L(zhǎng)ist<?>的子類。它的使用場(chǎng)景一般是泛型類中的方法不依賴于類型參數(shù)的時(shí)候,比如list.size(), 遍歷集合等,這樣的話并不關(guān)心List中元素的具體類型。
7?? 泛型中的PECS原則
PECS原則的全拼是"Producer Extends Consumer Super"。
案例分析:創(chuàng)建Apple,F(xiàn)ruit兩個(gè)類,其中Apple是Fruit的子類
public class PECS {
ArrayList<? extends Fruit> exdentFurit;
ArrayList<? super Fruit> superFurit;
Apple apple = new Apple();
private void test() {
Fruit a1 = exdentFurit.get(0);
Fruit a2 = superFurit.get(0); //Err1
exdentFurit.add(apple); //Err2
superFurit.add(apple);
}
}
其中Err1和Err2行處報(bào)錯(cuò),因?yàn)檫@些操作并不符合PECS原則,逐一分析:
Err1
使用? super T規(guī)定泛型的數(shù)據(jù)結(jié)構(gòu),其存儲(chǔ)的值是T的父類,而這里superFruit.get()的對(duì)象為Fruit的父類對(duì)象,而指向該對(duì)象的引用類型為Fruit,父類缺少子類中的一些信息,這顯然是不對(duì)的,因此編譯器直接禁止在使用? super T泛型的數(shù)據(jù)結(jié)構(gòu)中進(jìn)行取值,只能進(jìn)行寫值,正是開(kāi)頭所說(shuō)的CS原則。
Err2
使用? extends T規(guī)定泛型的數(shù)據(jù)結(jié)構(gòu),其存儲(chǔ)的值是T的子類,這里exdentFruit.add()也就是向其中添加Fruit的子類對(duì)象,而Fruit可以有多種子類對(duì)象,因此當(dāng)我們進(jìn)行寫值時(shí),我們并不知道其中存儲(chǔ)的到底是哪個(gè)子類,因此寫值操作必然會(huì)出現(xiàn)問(wèn)題,所以編譯器接禁止在使用? extends T泛型的數(shù)據(jù)結(jié)構(gòu)中進(jìn)行寫,只能進(jìn)行取值,正是開(kāi)頭所說(shuō)的PE原則。
8?? 類型擦除
類型擦除作用:因?yàn)镴ava中的泛型實(shí)在JDK1.5之后新加的特性,為了兼容性,在虛擬機(jī)中運(yùn)行時(shí)是不存在泛型的,所以Java泛型是一種偽泛型,類型擦除就保證了泛型不在運(yùn)行時(shí)候出現(xiàn)。
場(chǎng)景:編譯器會(huì)把泛型類型中所有的類型參數(shù)替換為它們的上(下)限,如果沒(méi)有對(duì)類型參數(shù)做出限制,那么就替換為Object類型。因此,編譯出的字節(jié)碼僅僅包含了常規(guī)類,接口和方法。
15. Java泛型的原理?什么是泛型擦除機(jī)制?
Java的泛型是JDK5新引入的特性,為了向下兼容,虛擬機(jī)其實(shí)是不支持泛型,所以Java實(shí)現(xiàn)的是一種偽泛型機(jī)制,也就是說(shuō)Java在編譯期擦除了所有的泛型信息,這樣Java就不需要產(chǎn)生新的類型到字節(jié)碼,所有的泛型類型最終都是一種原始類型,在Java運(yùn)行時(shí)根本就不存在泛型信息。
類型擦除其實(shí)在類常量池中保存了泛型信息,運(yùn)行時(shí)還能拿到信息,比如Gson的TypeToken的使用。
泛型算法實(shí)現(xiàn)的關(guān)鍵:利用受限類型參數(shù)。
16. Java編譯器具體是如何擦除泛型的
檢查泛型類型,獲取目標(biāo)類型
擦除類型變量,并替換為限定類型
如果泛型類型的類型變量沒(méi)有限定,則用Object作為原始類型
如果有限定,則用限定的類型作為原始類型
如果有多個(gè)限定(T extends Class1&Class2),則使用第一個(gè)邊界Class1作為原始類
在必要時(shí)插入類型轉(zhuǎn)換以保持類型安全
生成橋方法以在擴(kuò)展時(shí)保持多態(tài)性
17. Array數(shù)組中可以用泛型嗎?
不能,簡(jiǎn)單的來(lái)講是因?yàn)槿绻梢詣?chuàng)建泛型數(shù)組,泛型擦除會(huì)導(dǎo)致編譯能通過(guò),但是運(yùn)行時(shí)會(huì)出現(xiàn)異常。所以如果禁止創(chuàng)建泛型數(shù)組,就可以避免此類問(wèn)題。
18. PESC原則&限定通配符和非限定通配符
如果你只需要從集合中獲得類型T , 使用<? extends T>通配符
如果你只需要將類型T放到集合中, 使用<? super T>通配符
如果你既要獲取又要放置元素,則不使用任何通配符。例如List<String>
<?> 非限定通配符既不能存也不能取, 一般使用非限定通配符只有一個(gè)目的,就是為了靈活的轉(zhuǎn)型。其實(shí)List<?> 等于 List<? extends Object>。
19. Java中List<?>和List<Object>的區(qū)別
雖然他們都會(huì)進(jìn)行類型檢查,實(shí)質(zhì)上卻完全不同。List<?> 是一個(gè)未知類型的List,而List<Object>其實(shí)是任意類型的List。你可以把List<String>, List<Integer>賦值給List<?>,卻不能把List<String>賦值給List<Object>。
20. for循環(huán)和forEach效率問(wèn)題
== 遍歷ArrayList測(cè)試==
這里向ArrayList中插入10000000條數(shù)據(jù),分別用for循環(huán)和for each循環(huán)進(jìn)行遍歷測(cè)試
package for循環(huán)效率問(wèn)題;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < 10000000; i++) {
arrayList.add(i);
}
int x = 0;
//for循環(huán)遍歷
long forStart = System.currentTimeMillis();
for (int i = 0; i < arrayList.size(); i++) {
x = arrayList.get(i);
}
long forEnd = System.currentTimeMillis();
System.out.println("for循環(huán)耗時(shí)" + (forEnd - forStart) + "ms");
//for-each遍歷
long forEachStart = System.currentTimeMillis();
for (int i : arrayList) {
x = i;
}
long forEachEnd = System.currentTimeMillis();
System.out.println("foreach耗時(shí)" + (forEachEnd - forEachStart) + "ms");
}
}
根據(jù)執(zhí)行結(jié)果,可以看到for循環(huán)速度更快一點(diǎn),但是差別不太大

我們反編譯class文件看看
package for循環(huán)效率問(wèn)題;
import java.util.ArrayList;
import java.util.Iterator;
public class Test {
public Test() {
}
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList();
int x;
for(x = 0; x < 10000000; ++x) {
arrayList.add(x);
}
int x = false;
long forStart = System.currentTimeMillis();
for(int i = 0; i < arrayList.size(); ++i) {
x = (Integer)arrayList.get(i);
}
long forEnd = System.currentTimeMillis();
System.out.println("for循環(huán)耗時(shí)" + (forEnd - forStart) + "ms");
long forEachStart = System.currentTimeMillis();
int i;
for(Iterator var9 = arrayList.iterator();
var9.hasNext();
i = (Integer)var9.next()) {
}
long forEachEnd = System.currentTimeMillis();
System.out.println("foreach耗時(shí)" + (forEachEnd - forEachStart) + "ms");
}
}
可以看到增強(qiáng)for循環(huán)本質(zhì)上就是使用iterator迭代器進(jìn)行遍歷
== 遍歷LinkedList測(cè)試==
這里向LinkedList中插入測(cè)試10000條數(shù)據(jù)進(jìn)行遍歷測(cè)試,實(shí)驗(yàn)中發(fā)現(xiàn)如果循環(huán)次數(shù)太大,for循環(huán)直接卡死;
package for循環(huán)效率問(wèn)題;
import java.util.LinkedList;
public class Test2 {
public static void main(String[] args) {
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 10000; i++) {
linkedList.add(i);
}
int x = 0;
//for循環(huán)遍歷
long forStart = System.currentTimeMillis();
for (int i = 0; i < linkedList.size(); i++) {
x = linkedList.get(i);
}
long forEnd = System.currentTimeMillis();
System.out.println("for循環(huán)耗時(shí)" + (forEnd - forStart) + "ms");
//for-each遍歷
long forEachStart = System.currentTimeMillis();
for (int i : linkedList) {
x = i;
}
long forEachEnd = System.currentTimeMillis();
System.out.println("foreach耗時(shí)" + (forEachEnd - forEachStart) + "ms");
}
}
根據(jù)結(jié)果可以看到,遍歷LinkedList時(shí)for each速度遠(yuǎn)遠(yuǎn)大于for循環(huán)速度

反編譯class文件的源碼
Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//
package for循環(huán)效率問(wèn)題;
import java.util.Iterator;
import java.util.LinkedList;
public class Test2 {
public Test2() {
}
public static void main(String[] args) {
LinkedList<Integer> linkedList = new LinkedList();
int x;
for (x = 0; x < 10000; ++x) {
linkedList.add(x);
}
int x = false;
long forStart = System.currentTimeMillis();
for (int i = 0; i < linkedList.size(); ++i) {
x = (Integer) linkedList.get(i);
}
long forEnd = System.currentTimeMillis();
System.out.println("for循環(huán)耗時(shí)" + (forEnd - forStart) + "ms");
long forEachStart = System.currentTimeMillis();
int i;
for (Iterator var9 = linkedList.iterator(); var9.hasNext(); i = (Integer) var9.next()) {
}
long forEachEnd = System.currentTimeMillis();
System.out.println("foreach耗時(shí)" + (forEachEnd - forEachStart) + "ms");
}
}
== 總結(jié) ==
1?? 區(qū)別:
2?? 性能比對(duì):
對(duì)于arraylist底層為數(shù)組類型的結(jié)構(gòu),使用for循環(huán)遍歷比使用foreach循環(huán)遍歷稍快一些,但相差不大
對(duì)于linkedlist底層為單鏈表類型的結(jié)構(gòu),使用for循環(huán)每次都要從第一個(gè)元素開(kāi)始遍歷,速度非常慢;使用foreach可以直接讀取當(dāng)前結(jié)點(diǎn),速度比f(wàn)or快很多
3?? 原理接釋:
# 順序表a[3]
- 用for循環(huán),從a[0]開(kāi)始直接讀到元素,接著直接讀a[1](順序表的優(yōu)點(diǎn),隨機(jī)訪問(wèn))
- 用foreach,得到a[0]-a[2]的全部地址放入隊(duì)列,按順序取出隊(duì)里里的地址來(lái)訪問(wèn)元素
16. NIO、BIO、AIO
(1條消息) Netty_youthlql的博客-CSDN博客
尚硅谷Netty教程(B站最火,人氣最高,好評(píng)如潮)_嗶哩嗶哩 (゜-゜)つロ 干杯~-bilibili
阻塞IO 和 非阻塞IO
IO操作分為兩個(gè)部分,即發(fā)起IO請(qǐng)求和實(shí)際IO操作,阻塞IO和非阻塞IO的區(qū)別就在于第二個(gè)步驟是否阻塞
同步IO 和 異步IO
IO操作分為兩個(gè)部分,即發(fā)起IO請(qǐng)求和實(shí)際IO操作,同步IO和異步IO的區(qū)別就在于第一個(gè)步驟是否阻塞
NIO、BIO、AIO
BIO表示同步阻塞式IO,服務(wù)器實(shí)現(xiàn)模式為一個(gè)連接一個(gè)線程,即客戶端有連接請(qǐng)求時(shí)服務(wù)器端就需要啟動(dòng)一個(gè)線程進(jìn)行處理,如果這個(gè)連接不做任何事情會(huì)造成不必要的線程開(kāi)銷,當(dāng)然可以通過(guò)線程池機(jī)制改善。
NIO表示同步非阻塞IO,服務(wù)器實(shí)現(xiàn)模式為一個(gè)請(qǐng)求一個(gè)線程,即客戶端發(fā)送的連接請(qǐng)求都會(huì)注冊(cè)到多路復(fù)用器上,多路復(fù)用器輪詢到連接有I/O請(qǐng)求時(shí)才啟動(dòng)一個(gè)線程進(jìn)行處理。
AIO表示異步非阻塞IO,服務(wù)器實(shí)現(xiàn)模式為一個(gè)有效請(qǐng)求一個(gè)線程,客戶端的I/O請(qǐng)求都是由操作系統(tǒng)先完成IO操作后再通知服務(wù)器應(yīng)用來(lái)啟動(dòng)線程進(jìn)行處理。
17. 什么是反射
反射是在運(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ī)制。
反射實(shí)現(xiàn)了把java類中的各種結(jié)構(gòu)法、屬性、構(gòu)造器、類名)映射成一個(gè)個(gè)的Java對(duì)象
優(yōu)點(diǎn):可以實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建對(duì)象和編譯,體現(xiàn)了很大的靈活性
缺點(diǎn):對(duì)性能有影響,使用反射本質(zhì)上是一種接釋操作,慢于直接執(zhí)行java代碼
應(yīng)用場(chǎng)景:
JDBC中,利用反射動(dòng)態(tài)加載了數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序。
Web服務(wù)器中利用反射調(diào)用了Sevlet的服務(wù)方法。
Eclispe等開(kāi)發(fā)工具利用反射動(dòng)態(tài)刨析對(duì)象的類型與結(jié)構(gòu),動(dòng)態(tài)提示對(duì)象的屬性和方法。
很多框架都用到反射機(jī)制,注入屬性,調(diào)用方法,如Spring。
18. 序列化&反序列化
Java基礎(chǔ)學(xué)習(xí)總結(jié)——Java對(duì)象的序列化和反序列化 - 孤傲蒼狼 - 博客園 (cnblogs.com)
1?? 什么是序列化?
序列化是指將Java對(duì)象轉(zhuǎn)化為字節(jié)序列的過(guò)程,而反序列化則是將字節(jié)序列轉(zhuǎn)化為Java對(duì)象的過(guò)程
2?? 為什么需要序列化?
我們知道不同線程/進(jìn)程進(jìn)行遠(yuǎn)程通信時(shí)可以相互發(fā)送各種數(shù)據(jù),包括文本圖片音視頻等,Java對(duì)象不能直接傳輸,所以需要轉(zhuǎn)化為二進(jìn)制序列傳輸,所以需要序列化
3?? 序列化的用途?
把對(duì)象的字節(jié)序列永久地保存到硬盤上,通常存放在一個(gè)文件中
在很多應(yīng)用中,需要對(duì)某些對(duì)象進(jìn)行序列化,讓它們離開(kāi)內(nèi)存空間,入住物理硬盤,以便長(zhǎng)期保存。比如最常見(jiàn)的是Web服務(wù)器中的Session對(duì)象,當(dāng)有10萬(wàn)用戶并發(fā)訪問(wèn),就有可能出現(xiàn)10萬(wàn)個(gè)Session對(duì)象,內(nèi)存可能吃不消,于是Web容器就會(huì)把一些seesion先序列化到硬盤中,等要用了,再把保存在硬盤中的對(duì)象還原到內(nèi)存中
在網(wǎng)絡(luò)上傳送對(duì)象的字節(jié)序列
當(dāng)兩個(gè)進(jìn)程在進(jìn)行遠(yuǎn)程通信時(shí),彼此可以發(fā)送各種類型的數(shù)據(jù)。無(wú)論是何種類型的數(shù)據(jù),都會(huì)以二進(jìn)制序列的形式在網(wǎng)絡(luò)上傳送。發(fā)送方需要把這個(gè)Java對(duì)象轉(zhuǎn)換為字節(jié)序列,才能在網(wǎng)絡(luò)上傳送;接收方則需要把字節(jié)序列再恢復(fù)為Java對(duì)象
4?? JDK類庫(kù)中的序列化API
java.io.ObjectOutputStream代表對(duì)象輸出流,它的writeObject(Object obj)方法可對(duì)參數(shù)指定的obj對(duì)象進(jìn)行序列化,把得到的字節(jié)序列寫到一個(gè)目標(biāo)輸出流中
java.io.ObjectInputStream代表對(duì)象輸入流,它的readObject()方法從一個(gè)源輸入流中讀取字節(jié)序列,再把它們反序列化為一個(gè)對(duì)象,并將其返回
只有實(shí)現(xiàn)了Serializable和Externalizable接口的類的對(duì)象才能被序列化。Externalizable接口繼承自Serializable接口,實(shí)現(xiàn)Externalizable接口的類完全由自身來(lái)控制序列化的行為,而僅實(shí)現(xiàn)Serializable接口的類可以 采用默認(rèn)的序列化方式
對(duì)象序列化包括如下步驟:
創(chuàng)建一個(gè)對(duì)象輸出流,它可以包裝一個(gè)其他類型的目標(biāo)輸出流,如文件輸出流
通過(guò)對(duì)象輸出流的writeObject()方法寫對(duì)象
對(duì)象反序列化的步驟如下:
創(chuàng)建一個(gè)對(duì)象輸入流,它可以包裝一個(gè)其他類型的源輸入流,如文件輸入流
通過(guò)對(duì)象輸入流的readObject()方法讀取對(duì)象
5?? serialVersionUID的作用
serialVersionUID: 字面意思上是序列化的版本號(hào),凡是實(shí)現(xiàn)Serializable接口的類都有一個(gè)表示序列化版本標(biāo)識(shí)符的靜態(tài)變量
如果實(shí)現(xiàn)Serializable接口的類如果類中沒(méi)有添加serialVersionUID,那么就會(huì)出現(xiàn)警告提示
serialVersionUID有兩種生成方式:
采用Add default serial version ID方式生成的serialVersionUID是1L,例如:
private static final long serialVersionUID = 1L;
2.采用Add generated serial version ID這種方式生成的serialVersionUID是根據(jù)類名,接口名,方法和屬性等來(lái)生成的,例如:
private static final long serialVersionUID = 4603642343377807741L;
19. 動(dòng)態(tài)代理是什么?有哪些應(yīng)用?
當(dāng)想要給實(shí)現(xiàn)了某個(gè)接口的類中的方法,加一些額外的處理。比如說(shuō)加日志,加事務(wù)等。可以給這個(gè)類創(chuàng)建一個(gè)代理,故名思議就是創(chuàng)建一個(gè)新的類,這個(gè)類不僅包含原來(lái)類方法的功能,而且還在原來(lái)的基礎(chǔ)上添加了額外處理的新類。這個(gè)代理類并不是定義好的,是動(dòng)態(tài)生成的。具有解耦意義,靈活,擴(kuò)展性強(qiáng)。
應(yīng)用:
Spring的AOP
加事務(wù)
加權(quán)限
加日志
20. 怎么實(shí)現(xiàn)動(dòng)態(tài)代理
在java的java.lang.reflect包下提供了一個(gè)Proxy類和一個(gè)InvocationHandler接口,通過(guò)這個(gè)類和這個(gè)接口可以生成JDK動(dòng)態(tài)代理類和動(dòng)態(tài)代理對(duì)象
java.lang.reflect.Proxy是所有動(dòng)態(tài)代理的父類。它通過(guò)靜態(tài)方法newProxyInstance()來(lái)創(chuàng)建動(dòng)態(tài)代理的class對(duì)象和實(shí)例。
每一個(gè)動(dòng)態(tài)代理實(shí)例都有一個(gè)關(guān)聯(lián)的InvocationHandler。通過(guò)代理實(shí)例調(diào)用方法,方法調(diào)用請(qǐng)求會(huì)被轉(zhuǎn)發(fā)給InvocationHandler的invoke方法。
首先定義一個(gè)IncocationHandler處理器接口實(shí)現(xiàn)類,實(shí)現(xiàn)其invoke()方法
通過(guò)Proxy.newProxyInstance生成代理類對(duì)象
package demo3;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyInvocationHandler implements InvocationHandler {
//定義真實(shí)角色
private Rent host;
//真實(shí)角色set方法
public void setHost(Rent host) {
this.host = host;
}
/**
生成代理類方法
1. 類加載器,為當(dāng)前類即可
2. 代理類實(shí)現(xiàn)的接口
3. 處理器接口對(duì)象
**/
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
host.getClass().getInterfaces(), this);
}
//處理代理實(shí)例,并返回結(jié)果
//方法在此調(diào)用
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//調(diào)用真實(shí)角色方法,相當(dāng)于調(diào)用rent()方法
Object result = method.invoke(host, args);
//附加方法
seeHouse();
contract();
fare();
return result;
}
//看房
public void seeHouse() {
System.out.println("中介帶你看房");
}
//簽合同
public void contract() {
System.out.println("租賃合同");
}
//收中介費(fèi)
public void fare() {
System.out.println("收中介費(fèi)");
}
}
package demo3;
public class Client {
public static void main(String[] args) {
//真實(shí)角色:房東
Host host = new Host();
//處理器接口對(duì)象
ProxyInvocationHandler handler = new ProxyInvocationHandler();
//設(shè)置要代理的真實(shí)角色
handler.setHost(host);
//動(dòng)態(tài)生成代理類
Rent proxy = (Rent) handler.getProxy();
//調(diào)用方法
proxy.rent();
}
}
21. 如何實(shí)現(xiàn)對(duì)象克隆?
有兩種方式:
1.、實(shí)現(xiàn)Cloneable接口并重寫Object類中的clone()方法;
protected Object clone() throws CloneNotSupportedException {
test_paper paper = (test_paper) super.clone();
paper.date = (Date) date.clone();
return paper;
}
2、實(shí)現(xiàn)Serializable接口,通過(guò)對(duì)象的序列化和反序列化實(shí)現(xiàn)克隆,可以實(shí)現(xiàn)真正的深度克隆
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;
@SuppressWarnings("all")
public class Client {
public static void main(String[] args) throws Exception {
Date date = new Date();
String name = "zsr";
test_paper paper1 = new test_paper(name, date);
//通過(guò)序列化和反序列化來(lái)實(shí)現(xiàn)深克隆
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(bos);
obs.writeObject(paper1);
byte a[] = bos.toByteArray();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(a));
test_paper paper3 = (test_paper) ois.readObject();//獲取到新對(duì)象
paper3.getDate().setDate(1000);//改變非基本類型屬性
System.out.println(paper1);
System.out.println(paper3);
}
}
