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

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

- 重載方法的訪問控制符也可以不同,但是不能僅僅訪問控制符不同,否則編譯時報錯

8. Java異常體系

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

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

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

局部內(nèi)部類:

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


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

我們反編譯class文件看看
package for循環(huá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)耗時" + (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耗時" + (forEachEnd - forEachStart) + "ms");
}
}
可以看到增強for循環(huán)本質(zhì)上就是使用iterator迭代器進行遍歷
== 遍歷LinkedList測試==
這里向LinkedList中插入測試10000條數(shù)據(jù)進行遍歷測試,實驗中發(fā)現(xiàn)如果循環(huán)次數(shù)太大,for循環(huán)直接卡死;
package for循環(huá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)耗時" + (forEnd - forStart) + "ms");
//for-each遍歷
long forEachStart = System.currentTimeMillis();
for (int i : linkedList) {
x = i;
}
long forEachEnd = System.currentTimeMillis();
System.out.println("foreach耗時" + (forEachEnd - forEachStart) + "ms");
}
}
根據(jù)結(jié)果可以看到,遍歷LinkedList時for each速度遠遠大于for循環(huán)速度

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