Core Java 52 問(含答案)

上篇文章 4.9k Star 安卓面試知識(shí)點(diǎn),請(qǐng)收下! 翻譯了 Mindorks 的一份超強(qiáng)面試題,今天帶來(lái)的是其中 Core Java 部分 52 道題目的答案。題目的質(zhì)量還是比較高的,基本涵蓋了 Java 基礎(chǔ)知識(shí)點(diǎn),面向?qū)ο?、集合、基本?shù)據(jù)類型、并發(fā)、Java 內(nèi)存模型、GC、異常等等都有涉及。整理答案的過程中才發(fā)現(xiàn)自己也有一些知識(shí)點(diǎn)記不太清了,一邊回憶學(xué)習(xí),一邊整理答案。52 道題,可以代碼驗(yàn)證的都經(jīng)過我的驗(yàn)證,保證答案準(zhǔn)確。

文章比較長(zhǎng),翻到文末可以直接獲取 Core Java 52 問 pdf 文檔。

下面就進(jìn)入提問!

Core Java

面向?qū)ο?/h3>

1. 什么是 OOP ?

別說(shuō)我還真的被問到過這個(gè)問題,記得當(dāng)時(shí)我第一句話就是 “萬(wàn)物皆對(duì)象”。當(dāng)然答案很開放,說(shuō)說(shuō)你對(duì)面向?qū)ο蟮睦斫饩托辛?。下面是?維基百科
總結(jié)的答案:

Object-oriented programming ,面向?qū)ο蟪绦蛟O(shè)計(jì),是種具有對(duì)象概念的程序編程典范,同時(shí)也是一種程序開發(fā)的抽象方針。

它可能包含數(shù)據(jù) 、屬性 、代碼與方法。對(duì)象則指的是類的實(shí)例。它將對(duì)象作為程序的基本單元,將程序和數(shù)據(jù)封裝其中,以提高軟件的重用性、靈活性和擴(kuò)展性,

對(duì)象里的程序可以訪問及經(jīng)常修改對(duì)象相關(guān)連的數(shù)據(jù)。在面向?qū)ο蟪绦蚓幊汤?,?jì)算機(jī)程序會(huì)被設(shè)計(jì)成彼此相關(guān)的對(duì)象。

OOP 一般具有以下特征:

  • 類與對(duì)象

類定義了一件事物的抽象特點(diǎn)。類的定義包含了數(shù)據(jù)的形式以及對(duì)數(shù)據(jù)的操作。舉例來(lái)說(shuō), 這個(gè)類會(huì)包含狗的一切基礎(chǔ)特征,即所有 都共有的特征或行為,例如它的孕育、毛皮顏色和吠叫的能力。
對(duì)象就是類的實(shí)例。

  • 封裝

封裝(Encapsulation)是指 OOP 隱藏了某一方法的具體運(yùn)行步驟和實(shí)現(xiàn)細(xì)節(jié),限制只有特定類的對(duì)象可以訪問這一特定類的成員,通常暴露接口來(lái)供調(diào)用。每個(gè)人都知道怎么訪問它,但卻不必考慮它的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。

舉例來(lái)說(shuō), 這個(gè)類有 吠叫() 的方法,這一方法定義了狗具體該通過什么方法吠叫。但是,調(diào)用者并不知道它到底是如何吠叫的。

  • 繼承

繼承性(Inheritance)是指,在某種情況下,一個(gè)類會(huì)有 子類。子類比原本的類(稱為父類)要更加具體化。例如, 這個(gè)類可能會(huì)有它的子類 牧羊犬吉娃娃犬 。

子類會(huì)繼承父類的屬性和行為,并且也可包含它們自己的。這意味著程序員只需要將相同的代碼寫一次。

  • 多態(tài)

多態(tài)(Polymorphism)是指由繼承而產(chǎn)生的相關(guān)的不同的類,其對(duì)象對(duì)同一消息會(huì)做出不同的響應(yīng)。例如,狗和雞都有 叫() 這一方法,但是調(diào)用狗的 叫(),狗會(huì)吠叫;調(diào)用雞的 叫(),雞則會(huì)啼叫。

除了繼承,接口實(shí)現(xiàn),同一類中進(jìn)行方法重載也是多態(tài)的體現(xiàn)。

2. 抽象類和接口的區(qū)別 ?

  • 抽象類可以有默認(rèn)的方法實(shí)現(xiàn)。接口在 jdk1.8 之前沒有方法實(shí)現(xiàn),1.8 之后可以使用 default 關(guān)鍵字定義方法實(shí)現(xiàn)
  • 抽象類可以有構(gòu)造函數(shù),接口不可以
  • 子類使用 extends 關(guān)鍵字來(lái)繼承抽象類。如果子類不是抽象類的話,它需要提供抽象類中所有聲明的方法的實(shí)現(xiàn)。子類使用關(guān)鍵字 implements 來(lái)實(shí)現(xiàn)接口。它需要提供接口中所有聲明的方法的實(shí)現(xiàn)
  • 抽象方法可以有 publicprotecteddefault 這些修飾符,接口方法默認(rèn)是 public 的,可以缺省
  • 抽象類的字段可以使用任何修飾符。接口中字段默認(rèn)是 public final
  • 單繼承 多實(shí)現(xiàn)
  • 抽象類:is-a 的關(guān)系,體現(xiàn)的是一種關(guān)系的延續(xù)。
    接口: like-a 體現(xiàn)的是一種功能的擴(kuò)展關(guān)系

3. Iterator 和 Enumeration 的區(qū)別 ?

  • 函數(shù)接口不同
    Enumeration 只有 2 個(gè)函數(shù)接口。通過 Enumeration,我們只能讀取集合的數(shù)據(jù),而不能對(duì)數(shù)據(jù)進(jìn)行修改。

      Iterator 有 3 個(gè)函數(shù)接口。Iterator 除了能讀取集合的數(shù)據(jù)之外,也能數(shù)據(jù)進(jìn)行刪除操作。
    
  • Iterator 支持 fail-fast 機(jī)制,而 Enumeration 不支持。
    Enumeration 是 JDK 1.0 添加的接口。使用到它的函數(shù)包括 Vector 、Hashtable 等類,這些類都是 JDK 1.0 中加入的,Enumeration 存在的目的就是為它們提供遍歷接口。Enumeration 本身并沒有支持同步,而在 Vector 、Hashtable 實(shí)現(xiàn) Enumeration 時(shí),添加了同步。

      而 Iterator 是 JDK 1.2 才添加的接口,它也是為了 HashMap 、ArrayList 等集合提供遍歷接口。Iterator 是支持 fail-fast 機(jī)制的:當(dāng)多個(gè)線程對(duì)同一個(gè)集合的內(nèi)容進(jìn)行操作時(shí),就可能會(huì)產(chǎn)生 fail-fast 事件。
      
      所以 Enumeration 比 Iterator 的遍歷速度更快。
    

4. 你同意 組合優(yōu)先于繼承 嗎 ?

繼承的功能非常強(qiáng)大,但是也存在諸多問題,因?yàn)樗`背了封裝原則 。 只
有當(dāng)子類和超類之間確實(shí)存在子類型關(guān)系時(shí),使用繼承才是恰當(dāng)?shù)?。 即使如此,如果子
類和超類處在不同的包中,并且超類并不是為了繼承而設(shè)計(jì)的,那么繼承將會(huì)導(dǎo)致脆弱性
( fragility ) 。 為了避免這種脆弱性,可以用復(fù)合和轉(zhuǎn)發(fā)機(jī)制來(lái)代替繼承,尤其是當(dāng)存在適當(dāng)
的接口可以實(shí)現(xiàn)包裝類的時(shí)候 。 包裝類不僅比子類更加健壯,而且功能也更加強(qiáng)大。(也就是裝飾者模式)。

具體見 Effective Java 18條 復(fù)合優(yōu)先于繼承

5. 方法重載和方法重寫的區(qū)別 ?

同一個(gè)類中,方法名稱相同但是參數(shù)類型不同,稱為方法重載。
重載的方法在編譯過程中即可完成識(shí)別。具體到每一個(gè)方法調(diào)用,Java 編譯器會(huì)根據(jù)所傳入?yún)?shù)的聲明類型(注意與實(shí)際類型區(qū)分)來(lái)選取重載方法。

如果子類中定義了與父類中非私有方法同名的方法,而且這兩個(gè)方法參數(shù)類型不同,那么在子類中,這兩個(gè)方法同樣構(gòu)成了重載。反之,如果方法參數(shù)類型相同,
這時(shí)候要區(qū)分是否是靜態(tài)方法。如果是靜態(tài)方法,那么子類中的方法會(huì)隱藏父類的方法。如果不是靜態(tài)方法,就是子類重寫了父類的方法、

對(duì)重載方法的區(qū)分在編譯階段已經(jīng)完成,重載也被稱為靜態(tài)綁定,或者編譯時(shí)多態(tài)。重寫被稱為為動(dòng)態(tài)綁定。

6. 你知道哪些訪問修飾符 ? 它們分別的作用 ?

訪問級(jí)別 訪問控制修飾符 同類 同包 子類 不同的包
公開 public
受保護(hù) protected --
默認(rèn) 沒有訪問控制修飾符 -- --
私有 private -- -- --

7. 一個(gè)接口可以實(shí)現(xiàn)另一個(gè)接口嗎 ?

可以,但是不是 implements , 而是 extends 。一個(gè)接口可以繼承一個(gè)或多個(gè)接口。

8. 什么是多態(tài) ?什么是繼承 ?

在 java 中多態(tài)有編譯期多態(tài)(靜態(tài)綁定)和運(yùn)行時(shí)多態(tài)(動(dòng)態(tài)綁定)。方法重載是編譯期多態(tài)的一種形式。方法重寫是運(yùn)行時(shí)多態(tài)的一種形式。

多態(tài)的另一個(gè)重要例子是父類引用子類實(shí)例。事實(shí)上,滿足 is-a 關(guān)系的對(duì)象都可以看出多態(tài)。
例如,Cat 類 是 Animal 類的子類,所以 Cat is Animal,這就滿足了 is-a 關(guān)系。

繼承性(Inheritance)是指,在某種情況下,一個(gè)類會(huì)有“子類”。子類比原本的類(稱為父類)要更加具體化。例如,“狗”這個(gè)類可能會(huì)有它的子類“牧羊犬”和“吉娃娃犬”。
子類會(huì)繼承父類的屬性和行為,并且也可包含它們自己的。這意味著程序員只需要將相同的代碼寫一次。

9. Java 中類和接口的多繼承

在 java 中一個(gè)類不可以繼承多個(gè)類,但是接口可以繼承多個(gè)接口。

10. 什么是設(shè)計(jì)模式?

設(shè)計(jì)模式就不在這里展開說(shuō)了。推薦一個(gè) github 項(xiàng)目 java-design-patterns。
后面有機(jī)會(huì)單獨(dú)寫一寫設(shè)計(jì)模式。

集合和泛型

11. Arrays vs ArrayLists

Arrays 是一個(gè)工具類,提供了許多操作,排序,查找數(shù)組的靜態(tài)方法。

ArrayList 是一個(gè)動(dòng)態(tài)數(shù)組隊(duì)列,實(shí)現(xiàn)了 Collection 和 List 接口,提供了數(shù)據(jù)的增加,刪除,獲取等方法。

12. HashSet vs TreeSet

HashSetTreeSet 都是基于 Set 接口的實(shí)現(xiàn)類。其中 TreeSetSet 的子接口 SortedSet 的實(shí)現(xiàn)類。

HashSet 基于哈希表實(shí)現(xiàn),它不保證集合的迭代順序,特別是它不保證該順序恒久不變。允許 null 值。不支持同步。

TreeSet 基于二叉樹實(shí)現(xiàn),它的元素自動(dòng)排序,按照自然順序或者提供的比較器進(jìn)行排序,所以 TreeSet 中元素要實(shí)現(xiàn) Comparable 接口。不允許 null 值。

13. HashMap vs HashSet

HashMap HashSet
實(shí)現(xiàn)了 Map 接口 實(shí)現(xiàn)了 Set 接口
存儲(chǔ)鍵值對(duì) 僅存儲(chǔ)對(duì)象
調(diào)用 put() 向 map 中添加元素 調(diào)用 add() 方法向 Set中 添加元素
使用鍵對(duì)象來(lái)計(jì)算 hashcode 值 使用成員對(duì)象來(lái)計(jì)算 hashcode 值,對(duì)于兩個(gè)對(duì)象來(lái)說(shuō) hashcode 可能相同,所以 equals() 方法用來(lái)判斷對(duì)象的相等性,如果兩個(gè)對(duì)象不同的話,那么返回false
HashMap 相對(duì)于 HashSet 較快,因?yàn)樗鞘褂梦ㄒ坏逆I獲取對(duì)象 HashSet 較 HashMap 來(lái)說(shuō)比較慢

14. Stack vs Queue

隊(duì)列是一種基于先進(jìn)先出(FIFO)策略的集合類型。隊(duì)列在保存元素的同時(shí)保存它們的相對(duì)順序:使它們?nèi)肓许樞蚝统隽许樞蛳嗤j?duì)列在生活和編程中極其常見,就像排隊(duì),先進(jìn)入隊(duì)伍的總是先出去。

棧是一種基于后進(jìn)先出(LIFO)策略的集合類型,當(dāng)使用 foreach 語(yǔ)句遍歷棧中的元素時(shí),元素的處理順序和它們被壓入的順序正好相反。就像我們的郵箱,后進(jìn)來(lái)的郵件總是會(huì)先看到。

15. 解釋 java 中的泛型

16. String 類是如何實(shí)現(xiàn)的?它為什么被設(shè)計(jì)成不可變類 ?

String 類是使用 char 數(shù)組實(shí)現(xiàn)的,jdk 9 中改為使用 byte 數(shù)組實(shí)現(xiàn)。
不可變類好處:

  • 不可變類比較簡(jiǎn)單。
  • 不可變對(duì)象本質(zhì)上是線程安全的,它們不要求同步。不可變對(duì)象可以被自由地共享。
  • 不僅可以共享不可變對(duì)象,甚至可以共享它們的內(nèi)部信息。
  • 不可變對(duì)象為其他對(duì)象提供了大量的構(gòu)建。
  • 不可變類真正唯一的缺點(diǎn)是,對(duì)于每個(gè)不同的值都需要一個(gè)單獨(dú)的對(duì)象。

走進(jìn) JDK 之 String

對(duì)象和基本類型

17. 為什么說(shuō) String 不可變 ?

  • Stringfinal 類,不可以被擴(kuò)展
  • private final char value[],不可變
  • 沒有對(duì)外提供任何修改 value[] 的方法

參見我的文章 String 為什么不可變 ?

18. 什么是 String.intern() ? 何時(shí)使用? 為什么使用 ?

如果常量池中存在當(dāng)前字符串, 就會(huì)直接返回當(dāng)前字符串. 如果常量池中沒有此字符串, 會(huì)將此字符串放入常量池中后, 再返回。

將運(yùn)行時(shí)需要大量使用的字符串放入常量池。

深入解析 String.intern()

19. 列舉 8 種基本類型

基本類型 大小 最大值 最小值 包裝類 虛擬機(jī)中符號(hào)
boolean - - - Boolean Z
char 16 bits 65536 0 Character C
byte 8 bits 127 -128 Byte B
short 16 bits 215-1 - 215 Short S
int 32 bits 231-1 231 Integer I
long 64 bits 263-1 -263 Long J
float 32 bits 3.4028235e+38f -3.4028235e+38f Float F
double 64 bits 1.7976931348623157e+308 -1.7976931348623157e+308 Double D

20. int 和 Integer 區(qū)別

int 是基本數(shù)據(jù)類型,一般直接存儲(chǔ)在棧中,更加高效

Integer 是包裝類型,new 出來(lái)的對(duì)象存儲(chǔ)在堆中,比較耗費(fèi)資源

21. 什么是自動(dòng)裝箱拆箱 ?

把基本數(shù)據(jù)類型轉(zhuǎn)換成包裝類的過程叫做裝箱。

把包裝類轉(zhuǎn)換成基本數(shù)據(jù)類型的過程叫做拆箱。

在Java 1.5之前,要手動(dòng)進(jìn)行裝箱,

Integer i = new Integer(10);

java 1.5 中,提供了自動(dòng)拆箱與自動(dòng)裝箱功能。需要拆箱和裝箱的時(shí)候,會(huì)自動(dòng)進(jìn)行轉(zhuǎn)換。

Integer i =10;  //自動(dòng)裝箱
int b= i;     //自動(dòng)拆箱

自動(dòng)裝箱都是通過Integer.valueOf()方法來(lái)實(shí)現(xiàn)的,Integer的自動(dòng)拆箱都是通過integer.intValue來(lái)實(shí)現(xiàn)的。

關(guān)于 Java 基本類型可以看我的一篇總結(jié)文章:走進(jìn) JDK 之談?wù)劵绢愋?/a>

22. Java 中的類型轉(zhuǎn)換

賦值和方法調(diào)用轉(zhuǎn)換規(guī)則:從低位類型到高位類型自動(dòng)轉(zhuǎn)換;從高位類型到低位類型需要強(qiáng)制類型轉(zhuǎn)換:

  1. 布爾型和其它基本數(shù)據(jù)類型之間不能相互轉(zhuǎn)換;
  2. byte 型可以轉(zhuǎn)換為 short 、int 、longfloatdouble
  3. short 可轉(zhuǎn)換為 int 、long 、 floatdouble
  4. char 可轉(zhuǎn)換為 int 、longfloatdouble
  5. int 可轉(zhuǎn)換為 long 、floatdouble
  6. long 可轉(zhuǎn)換為 floatdouble
  7. float 可轉(zhuǎn)換為 double

基本類型 與 對(duì)應(yīng)包裝類 可自動(dòng)轉(zhuǎn)換,這是自動(dòng)裝箱和折箱的原理。

兩個(gè)引用類型間轉(zhuǎn)換:

  1. 子類能直接轉(zhuǎn)換為父類 或 接口類型

  2. 父類轉(zhuǎn)換為子類要強(qiáng)制類型轉(zhuǎn)換,且在運(yùn)行時(shí)若實(shí)際不是對(duì)應(yīng)的對(duì)象,會(huì)拋出 ClassCastException 運(yùn)行時(shí)異常;

23. Java 值傳遞還是引用傳遞 ?

值傳遞。

值傳遞(pass by value)是指在調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)復(fù)制一份傳遞到函數(shù)中,這樣在函數(shù)中如果對(duì)參數(shù)進(jìn)行修改,將不會(huì)影響到實(shí)際參數(shù)。

引用傳遞(pass by reference)是指在調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)的地址直接傳遞到函數(shù)中,那么在函數(shù)中對(duì)參數(shù)所進(jìn)行的修改,將影響到實(shí)際參數(shù)。

Java 調(diào)用方法傳遞的是實(shí)參引用的副本。

為什么說(shuō)Java中只有值傳遞。

24. 對(duì)象實(shí)例化和初始化之間的區(qū)別 ?

Initialization(實(shí)例化) 是創(chuàng)建新對(duì)象并且分配內(nèi)存的過程。新創(chuàng)建的變量必須顯示賦值,否則它將使用存儲(chǔ)在該內(nèi)存區(qū)域上的上一個(gè)變量包含的值。為了避免這個(gè)問題,Java 會(huì)給不同的數(shù)據(jù)類型賦予默認(rèn)值:

  • boolean defaults to false;
  • byte defaults to 0;
  • short defaults to 0;
  • int defaults to 0;
  • long defaults to 0L;
  • char defaults to \u0000;
  • float defaults to 0.0f;
  • double defaults to 0.0d;
  • object defaults to null.

Instantiation(初始化)是給已經(jīng)聲明的變量顯示賦值的過程。

int j;  // Initialized variable (int defaults to 0 right after)
j = 10; // Instantiated variable

25. 局部變量、實(shí)例變量以及類變量之間的區(qū)別?

局部變量?jī)H僅存在于創(chuàng)建它的方法中,他們被保存在棧內(nèi)存,在方法外無(wú)法獲得它們的引用。Java 的方法執(zhí)行不是依賴寄存器的,而是棧幀,每個(gè)方法的執(zhí)行和結(jié)束都伴隨著棧幀的入棧和出棧,也伴隨著局部變量的創(chuàng)建和釋放。

實(shí)例變量也就是成員變量,聲明在類中,依賴類實(shí)例而存在,不同類實(shí)例中變量值也可能不同。

類變量也就是靜態(tài)變量,在所有類實(shí)例中只有一個(gè)值,在一個(gè)地方改變它的值將會(huì)改變所有類實(shí)例中的值。

Java 內(nèi)存模型和垃圾收集器

26. 什么是垃圾收集器 ? 它是如何工作的 ?

Java 和 C++ 之前有一堵由內(nèi)存動(dòng)態(tài)分配和垃圾收集技術(shù)所圍成的高墻,墻外面的人想進(jìn)去,墻里面的人想出去。

垃圾收集器主要用來(lái)回收堆上的無(wú)用對(duì)象,Java 開發(fā)者只管創(chuàng)建和使用對(duì)象,JVM 來(lái)為你自動(dòng)分配和回收內(nèi)存。

JVM 通過可達(dá)性分析算法來(lái)判定對(duì)象是否存活。這個(gè)算法的基本思路就是通過一系列的稱為 GC Roots 的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當(dāng)一個(gè)對(duì)象到 GC Roots 沒有任何引用鏈相連(用圖論的話來(lái)說(shuō),就是從 GC Roots 到這個(gè)對(duì)象不可達(dá))時(shí),則證明此對(duì)象是不可用的。

即使在可達(dá)性分析算法中不可達(dá)的對(duì)象,也并非是 非死不可 的,這時(shí)候他們暫時(shí)處于緩刑階段,要真正宣告一個(gè)對(duì)象死亡,至少要經(jīng)歷兩次標(biāo)記過程。

更多詳細(xì)內(nèi)容可以閱讀 《深入理解 Java 虛擬機(jī)》 第三章 垃圾收集器與內(nèi)存分配策略 。

27. 什么是 java 內(nèi)存模型? 它遵循了什么原則?它的堆棧是如何組織的 ?

Java 虛擬機(jī)規(guī)范中試圖定義一種 Java 內(nèi)存模型(Java Memory Model,JMM)來(lái)屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實(shí)現(xiàn)讓 Java 程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問效果。JMM 是語(yǔ)言級(jí)的內(nèi)存模型,它確保在不同的編譯器和不同的處理器平臺(tái)上,通過禁止特定類型的編譯器重排序和處理器重排序,為程序員提供一致的內(nèi)存可見性保證。

JMM 內(nèi)存模型的抽象表示如下:

image

結(jié)合上圖,在 Java 中,所有實(shí)例域、靜態(tài)域和數(shù)組元素都存儲(chǔ)在堆內(nèi)存中,堆內(nèi)存在線程之間共享。局部變量、方法定義參數(shù)和異常處理器參數(shù)在棧中,不會(huì)在線程之間共享,它們不會(huì)有內(nèi)存可見性問題,也不會(huì)受內(nèi)存模型影響。

Java 線程之間的通信由 JMM 控制,JMM 決定一個(gè)線程對(duì)共享變量的寫入何時(shí)對(duì)另一個(gè)線程可見。JMM 通過控制主內(nèi)存與每個(gè)線程的本地內(nèi)存之間的交互,來(lái)為 Java 程序員提供內(nèi)存可見性保證。

更多詳細(xì)內(nèi)容可以閱讀 《Java 并發(fā)編程的藝術(shù)》。

28. 什么是 內(nèi)存泄漏,java 如何處理它 ?

內(nèi)存泄露就是不會(huì)再被使用的對(duì)象無(wú)法被 GC 回收,即這些對(duì)象在可達(dá)性分析中是可達(dá)的,但在程序中的確不會(huì)再被使用。比如長(zhǎng)生命周期的對(duì)象引用了短生命周期的對(duì)象,導(dǎo)致短生命周期對(duì)象不能被回收。

Java 應(yīng)該不會(huì)處理內(nèi)存泄漏,我們能做的更多是防患于未然,以及使用合理手段監(jiān)測(cè),比如 Android 里常用的 LeakCanary,詳細(xì)原理可以看我之前的一篇文章 LeakCanary 源碼解析 。

29. 什么是 強(qiáng)引用,軟引用,弱引用,虛引用 ?

在 JDK 1.2 之后,Java 對(duì)引用的概念進(jìn)行了擴(kuò)充,將引用分為 強(qiáng)引用、軟引用、弱引用、虛引用 4 種,這 4 種引用強(qiáng)度依次逐漸減弱。

  • 強(qiáng)引用就是指程序代碼之中普遍存在的,類似 Object obj = new Object() 這類的引用,只要強(qiáng)引用還存在,GC 永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。

  • 軟引用是用來(lái)描述一些還有用但并非必須的對(duì)象。對(duì)于軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收。如果這次回收還沒有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常。在 JDK 1.2 之后,提供了 SoftReference 類來(lái)實(shí)現(xiàn)軟引用。

  • 弱引用也是用來(lái)描述非必須對(duì)象的,但它的強(qiáng)度比軟引用要弱一些,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次 GC 發(fā)生之前。當(dāng) GC 工作時(shí),無(wú)法當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象。在 JDK 1.2 之后,提供了 WeakReference 類來(lái)實(shí)現(xiàn)弱引用。

  • 虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系。一個(gè)對(duì)象是否有虛引用的存在,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無(wú)法通過虛引用來(lái)取得一個(gè)對(duì)象實(shí)例。為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象被 GC 回收時(shí)收到一個(gè)系統(tǒng)通知。在 JDK 1.2 之后,提供了 PhantomReference 類來(lái)實(shí)現(xiàn)虛引用。

并發(fā)

30. 關(guān)鍵字 synchronized 的作用 ?

關(guān)鍵字 synchronized 可以修飾方法或者以同步塊的形式來(lái)進(jìn)行使用,它主要確保多個(gè)線程在同一時(shí)刻,只能有一個(gè)線程處于方法或同步塊中,它保證了線程對(duì)變量訪問的可見性和排他性。

  • 對(duì)于普通同步方法,鎖是當(dāng)前實(shí)例對(duì)象
  • 對(duì)于靜態(tài)同步方法,鎖是當(dāng)前類的 Class 對(duì)象
  • 對(duì)于同步方法塊,鎖是 Synchonized 括號(hào)里配置的對(duì)象

31. ThreadPoolExecutor 作用 ?

在 Java 中,使用線程來(lái)執(zhí)行異步任務(wù)。Java 線程的創(chuàng)建與銷毀需要一定的開銷,如果我們?yōu)槊恳粋€(gè)任務(wù)都創(chuàng)建一個(gè)新線程來(lái)執(zhí)行,這些線程的創(chuàng)建與銷毀將消耗大量的計(jì)算資源。同時(shí),為每一個(gè)任務(wù)創(chuàng)建一個(gè)新線程來(lái)執(zhí)行,這種策略可能會(huì)使處于高負(fù)荷的應(yīng)用最終崩潰。

關(guān)于線程池的詳細(xì)介紹,推薦一篇文章 Java并發(fā)編程:線程池的使用

32. 關(guān)鍵字 volatile 的作用 ?

volatile 是輕量級(jí)的 synchronized,它在多處理器開發(fā)中保證了共享變量的 可見性。volatile 用來(lái)修飾字段(成員變量),就是告知程序任何對(duì)該變量的訪問均需從共享內(nèi)存中獲取,而對(duì)它的改變必須同步刷新回共享內(nèi)存,它能保證所有線程對(duì)變量訪問的可見性。

但是,過多的使用 volatile 是不必要的,因?yàn)樗鼤?huì)降低程序執(zhí)行的效率。

異常

33. try{} catch{} finally{} 是如何工作的 ?

try 代碼塊用來(lái)標(biāo)記需要進(jìn)行異常監(jiān)控的代碼

catch 代碼塊跟在 try 代碼塊之后,用來(lái)捕獲 try 代碼塊中觸發(fā)的某種指定類型的異常。除了聲明所捕獲的異常類型之外,catch 代碼塊還定義了針對(duì)該異常類型的異常處理器。在 Java 中,try 代碼塊后面可以跟著多個(gè) catch 代碼塊,來(lái)捕獲不同的異常。Java 虛擬機(jī)會(huì)從上至下匹配異常處理器。因此,前面的 catch 代碼塊所捕獲的異常類型不能覆蓋后面的,否則編譯器會(huì)報(bào)錯(cuò)。

finally 代碼塊跟在 try 代碼塊和 catch 代碼塊之后,用來(lái)聲明一段必定運(yùn)行的代碼。它的設(shè)計(jì)初衷是為了避免跳過某些關(guān)鍵的清理代碼,例如關(guān)閉已打開的系統(tǒng)資源。

在編譯生成的字節(jié)碼中,每個(gè)方法都附帶一個(gè)異常表。異常表中的每一個(gè)條目代表一個(gè)異常處理器,并且由 from 指針,to 指針, target 指針以及所捕獲的異常類型構(gòu)成。這些指針的值是字節(jié)碼索引(bytecode index,bci),用以定位字節(jié)碼。

其中,from 指針和 to 指針標(biāo)示了該異常處理器所監(jiān)控的范圍,例如 try 代碼塊所覆蓋的范圍。target 指針則指向異常處理器的起始位置,比如 catch 代碼塊的起始位置。

finally 代碼塊的編譯比較復(fù)雜。當(dāng)前版本 Java 編譯器的做法,是復(fù)制 finally 代碼塊的內(nèi)容,分別放在try-catch 代碼塊所有正常執(zhí)行路徑以及異常執(zhí)行路徑的出口中。

以上內(nèi)容來(lái)自極客時(shí)間專欄 深入拆解 Java 虛擬機(jī)

34. Checked Exception 和 Un-Checked Exception 區(qū)別 ?

在 Java 中,所有異常都是 Throwable 類或者其子類的實(shí)例。Throwable 有兩大直接子類。一個(gè)是 Error,涵蓋程序不應(yīng)捕獲的異常。當(dāng) Error 發(fā)生時(shí),它的執(zhí)行狀態(tài)已經(jīng)無(wú)法恢復(fù),需要終止線程甚至虛擬機(jī)。第二個(gè)子類是 Exception,涵蓋程序可能需要捕獲并且處理的異常。

Exception 有一個(gè)特殊的子類 RuntimeException,運(yùn)行時(shí)異常,用來(lái)表示 “程序雖然無(wú)法繼續(xù)執(zhí)行,但還能搶救一下” 的情況。

RuntimeException 和 Error 屬于 Java 里的非檢查異常(unchecked exception)。其他異常則屬于檢查異常(checked exception)。在 Java 語(yǔ)法中,所有的檢查異常都需要程序顯式地捕獲,或者在方法聲明中用 throws 關(guān)鍵字標(biāo)注。通常情況下,程序中自定義的異常應(yīng)為檢查異常,以便最大化利用 Java 編譯器的編譯時(shí)檢查。

以上內(nèi)容來(lái)自極客時(shí)間專欄 深入拆解 Java 虛擬機(jī)。

其他

35. 什么是序列化?如何實(shí)現(xiàn) ?

序列化是將對(duì)象轉(zhuǎn)換成字節(jié)流以便持久化存儲(chǔ)的過程。它可以保存對(duì)象的狀態(tài)和數(shù)據(jù),方便在特定時(shí)刻重新構(gòu)建該對(duì)象。在 Android 中,一般使用 Serializable , Externalizable (implements Serializable) 或者 Parcelable 接口。

Serializable 最容易實(shí)現(xiàn),直接實(shí)現(xiàn)接口即可。Externalizable 可以在序列化的過程中插入一些自己的邏輯代碼,考慮到它是 Java 早期版本的遺留物,現(xiàn)在基本已經(jīng)沒人再使用它。在 Android 中推薦使用 Parcelable ,它就是為 Android 而實(shí)現(xiàn),性能是 Serializable 的十倍,因?yàn)?Serializable 使用了反射。反射不僅慢,還會(huì)創(chuàng)建大量臨時(shí)對(duì)象,導(dǎo)致頻繁 GC。

例子:

/**
*  Implementing the Serializeable interface is all that is required
*/
public class User implements Serializable {

    private String name;
    private String email;

        public User() {
        }

        public String getName() {
            return name;
        }

        public void setName(final String name) {
            this.name = name;
        }

        public String getEmail() {
            return email;
        }

        public void setEmail(final String email) {
            this.email = email;
        }
    }

Parcelable 需要多一些工作:

public class User implements Parcelable {

        private String name;
        private String email;

        /**
         * Interface that must be implemented and provided as a public CREATOR field
         * that generates instances of your Parcelable class from a Parcel.
         */
        public static final Creator<User> CREATOR = new Creator<User>() {

            /**
             * Creates a new USer object from the Parcel. This is the reason why
             * the constructor that takes a Parcel is needed.
             */
            @Override
            public User createFromParcel(Parcel in) {
                return new User(in);
            }

            /**
             * Create a new array of the Parcelable class.
             * @return an array of the Parcelable class,
             * with every entry initialized to null.
             */
            @Override
            public User[] newArray(int size) {
                return new User[size];
            }
        };

        public User() {
        }

        /**
         * Parcel overloaded constructor required for
         * Parcelable implementation used in the CREATOR
         */
        private User(Parcel in) {
            name = in.readString();
            email = in.readString();
        }

        public String getName() {
            return name;
        }

        public void setName(final String name) {
            this.name = name;
        }

        public String getEmail() {
            return email;
        }

        public void setEmail(final String email) {
            this.email = email;
        }

        @Override
        public int describeContents() {
            return 0;
        }

        /**
         * This is where the parcel is performed.
         */
        @Override
        public void writeToParcel(final Parcel parcel, final int i) {
            parcel.writeString(name);
            parcel.writeString(email);
        }
    }

36. 關(guān)鍵字 transient 的作用 ?

transient 很簡(jiǎn)單,它的作用就是讓被其修飾的成員變量在序列化的過程中不被序列化。

37. 什么是匿名內(nèi)部類 ?

匿名內(nèi)部類是唯一一種沒有構(gòu)造器的類。正因?yàn)槠錄]有構(gòu)造器,所以匿名內(nèi)部類的使用范圍非常有限,大部分匿名內(nèi)部類用于接口回調(diào)。匿名內(nèi)部類在編譯的時(shí)候由系統(tǒng)自動(dòng)起名為 Outter$1.class。一般來(lái)說(shuō),匿名內(nèi)部類用于繼承其他類或是實(shí)現(xiàn)接口,并不需要增加額外的方法,只是對(duì)繼承方法的實(shí)現(xiàn)或是重寫。

Android 中應(yīng)用最常見的就是各種點(diǎn)擊事件。

38. 對(duì)象的 == 和 .equals 區(qū)別 ?

對(duì)于對(duì)象而言,== 永遠(yuǎn)比較的都是其內(nèi)存地址。而 equals() 則要看該對(duì)象是否重寫了 equals() 方法,如果沒有則會(huì)調(diào)用父類的 equals() 方法,如果父類也沒有實(shí)現(xiàn)的話,就不斷向上追溯,直至 Object 類??匆幌?Object.java 中的 equals() 方法:

public boolean equals(Object obj) {
    return (this == obj);
}

Object 中,equals 等同于 ==,都是比較內(nèi)存地址。再看一下不是比較內(nèi)存地址的,String.equals()

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

這里比較的就不再是內(nèi)存地址,而是其實(shí)際的值。

39. hashCode() 和 equals() 用處 ?

equals() 用于判斷兩個(gè)對(duì)象是否相等,未被重寫的話就是判斷內(nèi)存地址,和 == 語(yǔ)義一致。重寫了的話,就按照重寫的邏輯進(jìn)行判斷。

hashCode() 用于計(jì)算對(duì)象的哈希碼,默認(rèn)實(shí)現(xiàn)是將對(duì)象的內(nèi)存地址作為哈希碼返回,可以保證不同對(duì)象的返回值不同。理論上,hashCode 也可以用來(lái)比較對(duì)象是否相等。hashCode() 主要用在哈希表中,比如 HashMapHashSet 等。

當(dāng)我們向哈希表(如HashSet、HashMap等)中添加對(duì)象object時(shí),首先調(diào)用hashCode()方法計(jì)算object的哈希碼,通過哈希碼可以直接定位object在哈希表中的位置(一般是哈希碼對(duì)哈希表大小取余)。如果該位置沒有對(duì)象,可以直接將object插入該位置;如果該位置有對(duì)象(可能有多個(gè),通過鏈表實(shí)現(xiàn)),則調(diào)用equals()方法比較這些對(duì)象與object是否相等,如果相等,則不需要保存object;如果不相等,則將該對(duì)象加入到鏈表中。

equals() 相等,hashCode 必然相等。反之則不然,hashCode 相等,equals() 不能保證一定相等。

40. 構(gòu)造函數(shù)中為什么不能調(diào)用抽象方法 ?

構(gòu)造函數(shù)中不能調(diào)用抽象方法,說(shuō)的更嚴(yán)謹(jǐn)一點(diǎn),構(gòu)造函數(shù)中不能調(diào)用可被覆蓋的方法

先看這樣一個(gè)例子:

public abstract class Super {

    Super(){
        overrideMe();
    }

    abstract void overrideMe();
}

public class Sub extends Super {

    private final Instant instant;

    public Sub() {
        instant = Instant.now();
    }

    @Override
    void overrideMe() {
        System.out.println(instant);
    }

    public static void main(String[] args) {
        Sub sub=new Sub();
        sub.overrideMe();
    }

}

最后的打印結(jié)果:

null
2019-04-01T02:42:13.947Z

第一次打印出的是 null,因?yàn)?overrideMe 方法被 Super 構(gòu)造器調(diào)用的時(shí)候,構(gòu)造器 Sub 還沒有機(jī)會(huì)初始化 instant 域 。
注意,這個(gè)程序觀察到的 final 域處于兩種不同的狀態(tài) 。

超類的構(gòu)造器在子類的構(gòu)造器之前運(yùn)行,所以,子類中覆蓋版本的方法將會(huì)在子類的構(gòu)造器運(yùn)行之前先被
調(diào)用 。 如果該覆蓋版本的方法依賴于子類構(gòu)造器所執(zhí)行的任何初始化工作,該方法將不會(huì)如預(yù)期般執(zhí)行 。

41. 你什么時(shí)候會(huì)使用 final 關(guān)鍵字 ?

對(duì)于一個(gè) final 變量,如果是基本數(shù)據(jù)類型的變量,則其數(shù)值一旦在初始化之后便不能更改;如果是引用類型的變量,則在對(duì)其初始化之后便不能再讓其指向另一個(gè)對(duì)象。

另外,匿名內(nèi)部類中使用的外部局部變量只能是 final 變量。

當(dāng) final 變量是基本數(shù)據(jù)類型以及 String 類型時(shí),如果在編譯期間能知道它的確切值,則編譯器會(huì)把它當(dāng)做編譯期常量使用。

final 修飾方法參數(shù)也是為了強(qiáng)調(diào)參數(shù)不可改變。

final 修飾類表示類不可被繼承。

淺析 Java 的 final 關(guān)鍵字

42. final, finally 和 finalize 的區(qū)別 ?

final 和 finally 就不再說(shuō)了。重點(diǎn)看看 finalize。

如果類中重寫了 finalize 方法,當(dāng)該類對(duì)象被回收時(shí),finalize 方法有可能會(huì)被觸發(fā)。
Effective Java 中明確說(shuō)明 終結(jié)方法(finalize)通常是不可預(yù)測(cè)的,也是很危險(xiǎn)的,一般情況下是不必要的。

JVM 不僅不保證 finalize 方法可以被及時(shí)執(zhí)行,而且根本就不保證它們會(huì)被執(zhí)行。所以不要依賴 finalize 方法來(lái)做一些例如
釋放資源的操作??赡軙?huì)延時(shí)對(duì)象的回收,造成性能損失。

43. Java 中 static 關(guān)鍵字的含義 ?

static 就是為了方便在沒有創(chuàng)建對(duì)象的情況下來(lái)進(jìn)行調(diào)用(方法/變量)。

  • static 方法一般稱作靜態(tài)方法,由于靜態(tài)方法不依賴于任何對(duì)象就可以進(jìn)行訪問,因此對(duì)于靜態(tài)方法來(lái)說(shuō),是沒有 this 的,因?yàn)樗灰栏接谌魏螌?duì)象,既然都沒有對(duì)象,就談不上 this 了。并且由于這個(gè)特性,在靜態(tài)方法中不能訪問類的非靜態(tài)成員變量和非靜態(tài)成員方法,因?yàn)榉庆o態(tài)成員方法/變量都是必須依賴具體的對(duì)象才能夠被調(diào)用。

  • static 變量也稱作靜態(tài)變量,靜態(tài)變量和非靜態(tài)變量的區(qū)別是:靜態(tài)變量被所有的對(duì)象所共享,在內(nèi)存中只有一個(gè)副本,它當(dāng)且僅當(dāng)在類初次加載時(shí)會(huì)被初始化。而非靜態(tài)變量是對(duì)象所擁有的,在創(chuàng)建對(duì)象的時(shí)候被初始化,存在多個(gè)副本,各個(gè)對(duì)象擁有的副本互不影響。

  • static 關(guān)鍵字還有一個(gè)比較關(guān)鍵的作用就是 用來(lái)形成靜態(tài)代碼塊以優(yōu)化程序性能。static 塊可以置于類中的任何地方,類中可以有多個(gè) static 塊。在類初次被加載的時(shí)候,會(huì)按照 static 塊的順序來(lái)執(zhí)行每個(gè) static 塊,只會(huì)在類加載的時(shí)候執(zhí)行一次。

static 成員變量的初始化順序按照定義的順序進(jìn)行初始化。

44. 靜態(tài)方法可以重寫嗎 ?

你可以重寫,但這并不是多態(tài)的體現(xiàn),并不是真正意義上的重寫。子類的靜態(tài)方法會(huì)隱藏父類的靜態(tài)方法,這兩個(gè)方法并沒有什么關(guān)系,具體調(diào)用哪一個(gè)方法是看調(diào)用者是哪個(gè)對(duì)象的引用,并不存在多態(tài)。只有普通的方法調(diào)用才可以是多態(tài)的。

45. 靜態(tài)代碼塊如何運(yùn)行 ?

靜態(tài)代碼塊隨著類的加載而執(zhí)行,而且只執(zhí)行一次。

靜態(tài)代碼塊經(jīng)過編譯后是放在 <clinit> 中, <clinit> 在jvm第一次加載class文件時(shí)調(diào)用,包括靜態(tài)變量初始化語(yǔ)句和靜態(tài)塊的執(zhí)行。

46. 什么是反射 ?

反射 (Reflection) 是 Java 的特征之一,它允許運(yùn)行中的 Java 程序獲取自身的信息,并且可以操作類或?qū)ο蟮膬?nèi)部屬性。

Oracle 官方對(duì)反射的解釋是:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

簡(jiǎn)而言之,通過反射,我們可以在運(yùn)行時(shí)獲得程序或程序集中每一個(gè)類型的成員和成員的信息。程序中一般的對(duì)象的類型都是在編譯期就確定下來(lái)的,而 Java 反射機(jī)制可以動(dòng)態(tài)地創(chuàng)建對(duì)象并調(diào)用其屬性,這樣的對(duì)象的類型在編譯期是未知的。所以我們可以通過反射機(jī)制直接創(chuàng)建對(duì)象,即使這個(gè)對(duì)象的類型在編譯期是未知的。

反射的核心是 JVM 在運(yùn)行時(shí)才動(dòng)態(tài)加載類或調(diào)用方法/訪問屬性,它不需要事先(寫代碼的時(shí)候或編譯期)知道運(yùn)行對(duì)象是誰(shuí)。

Java 反射主要提供以下功能:

在運(yùn)行時(shí)判斷任意一個(gè)對(duì)象所屬的類;
在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對(duì)象;
在運(yùn)行時(shí)判斷任意一個(gè)類所具有的成員變量和方法(通過反射甚至可以調(diào)用private方法);
在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法

深入 Java 反射

IDE 的智能提示就是利用反射。

47. 什么是依賴注入 ?列舉幾個(gè)庫(kù) ?你使用過嗎 ?

理解的還不夠透徹,放上來(lái)一篇網(wǎng)上的寫的不錯(cuò)的文章: 輕松理解 Java開發(fā)中的依賴注入(DI)和控制反轉(zhuǎn)(IOC)

48. StringBuilder 如何避免不可變類 String 的分配問題?

StringBuilder 內(nèi)部維護(hù)了一個(gè)可變長(zhǎng)的 char[],用來(lái)存儲(chǔ)和拼接字符串,從而避免了因 String 是不可變類帶來(lái)的頻繁創(chuàng)建 String 對(duì)象的問題。

49. StringBuffer 和 StringBuilder 區(qū)別 ?

StringBufferStringBuilder 在使用上基本沒有區(qū)別。StringBuffer 通過 synchronized 關(guān)鍵字保證了線程安全,而 StringBuilder 沒有任何同步操作。所以在確定無(wú)線程同步問題時(shí),使用 StringBuilder 效率更高。

50. Enumeration and an Iterator 區(qū)別 ?

重復(fù)了,見第 3 題。

51. fail-fast and fail-safe 區(qū)別 ?

fail-fast 機(jī)制在遍歷一個(gè)集合時(shí),當(dāng)集合結(jié)構(gòu)被修改,會(huì)拋出 Concurrent Modification Exception。迭代器在遍歷過程中是直接訪問內(nèi)部數(shù)據(jù)的,因此內(nèi)部的數(shù)據(jù)在遍歷的過程中無(wú)法被修。
為了保證不被修改,迭代器內(nèi)部維護(hù)了一個(gè)標(biāo)記 “mode” ,當(dāng)集合結(jié)構(gòu)改變(添加刪除或者修改),標(biāo)記 "mode" 會(huì)被修改,
而迭代器每次的 hasNext()next() 方法都會(huì)檢查該 "mode" 是否被改變,當(dāng)檢測(cè)到被修改時(shí),拋出 Concurrent Modification Exception。

fail-safe 任何對(duì)集合結(jié)構(gòu)的修改都會(huì)在一個(gè)復(fù)制的集合上進(jìn)行修改,因此不會(huì)拋出 ConcurrentModificationException。

fail-safe 機(jī)制有兩個(gè)問題:

  1. 需要復(fù)制集合,產(chǎn)生大量的無(wú)效對(duì)象,開銷大

  2. 無(wú)法保證讀取的數(shù)據(jù)是目前原始數(shù)據(jù)結(jié)構(gòu)中的數(shù)據(jù)

52. 什么是 NIO ?

在 JDK 1. 4 中 新 加入 了 NIO( New Input/ Output) 類, 引入了一種基于通道和緩沖區(qū)的 I/O 方式,
它可以使用 Native 函數(shù)庫(kù)直接分配堆外內(nèi)存,然后通過一個(gè)存儲(chǔ)在 Java 堆的 DirectByteBuffer 對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作,
避免了在 Java 堆和 Native 堆中來(lái)回復(fù)制數(shù)據(jù)。

NIO 是一種同步非阻塞的 IO 模型。同步是指線程不斷輪詢 IO 事件是否就緒,非阻塞是指線程在等待 IO 的時(shí)候,可以同時(shí)做其他任務(wù)。
同步的核心就是 Selector,Selector 代替了線程本身輪詢 IO 事件,避免了阻塞同時(shí)減少了不必要的線程消耗;非阻塞的核心就是通道和緩沖區(qū),
當(dāng) IO 事件就緒時(shí),可以通過寫道緩沖區(qū),保證 IO 的成功,而無(wú)需線程阻塞式地等待。

深入理解 Java NIO

End

文章首發(fā)于微信公眾號(hào): 秉心說(shuō) , 專注 Java 、 Android 原創(chuàng)知識(shí)分享,LeetCode 題解,歡迎關(guān)注!

green.png

微信搜索 秉心說(shuō), 或者掃碼關(guān)注,回復(fù) Core Java 即可領(lǐng)取所有回答 pdf 文檔 。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容