Core Java 52 問(含答案)

上篇文章 4.9k Star 安卓面試知識點,請收下! 翻譯了 Mindorks 的一份超強面試題,今天帶來的是其中 Core Java 部分 52 道題目的答案。題目的質量仍是比較高的,基本涵蓋了 Java 基礎知識點,面向對象、集合、基本數據類型、併發、Java 內存模型、GC、異常等等都有涉及。整理答案的過程當中才發現本身也有一些知識點記不太清了,一邊回憶學習,一邊整理答案。52 道題,能夠代碼驗證的都通過個人驗證,保證答案准確。html

文章比較長,翻到文末能夠直接獲取 Core Java 52 問 pdf 文檔。java

下面就進入提問!git

Core Java

面向對象

1. 什麼是 OOP ?

別說我還真的被問到過這個問題,記得當時我第一句話就是 「萬物皆對象」。固然答案很開放,說說你對面向對象的理解就好了。下面是從 維基百科 總結的答案:程序員

Object-oriented programming ,面向對象程序設計,是種具備對象概念的程序編程典範,同時也是一種程序開發的抽象方針。github

它可能包含數據 、屬性 、代碼與方法。對象則指的是類的實例。它將對象做爲程序的基本單元,將程序和數據封裝其中,以提升軟件的重用性、靈活性和擴展性,面試

對象裏的程序能夠訪問及常常修改對象相關連的數據。在面向對象程序編程裏,計算機程序會被設計成彼此相關的對象。算法

OOP 通常具備如下特徵:編程

  • 類與對象

類定義了一件事物的抽象特色。類的定義包含了數據的形式以及對數據的操做。舉例來講, 這個類會包含狗的一切基礎特徵,即全部 都共有的特徵或行爲,例如它的孕育、毛皮顏色和吠叫的能力。 對象就是類的實例。設計模式

  • 封裝

封裝(Encapsulation)是指 OOP 隱藏了某一方法的具體運行步驟和實現細節,限制只有特定類的對象能夠訪問這一特定類的成員,一般暴露接口來供調用。每一個人都知道怎麼訪問它,但卻沒必要考慮它的內部實現細節。數組

舉例來講, 這個類有 吠叫() 的方法,這一方法定義了狗具體該經過什麼方法吠叫。可是,調用者並不知道它究竟是如何吠叫的。

  • 繼承

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

子類會繼承父類的屬性和行爲,而且也可包含它們本身的。這意味着程序員只須要將相同的代碼寫一次。

  • 多態

多態(Polymorphism)是指由繼承而產生的相關的不一樣的類,其對象對同一消息會作出不一樣的響應。例如,狗和雞都有 叫() 這一方法,可是調用狗的 叫(),狗會吠叫;調用雞的 叫(),雞則會啼叫。

除了繼承,接口實現,同一類中進行方法重載也是多態的體現。

2. 抽象類和接口的區別 ?

  • 抽象類能夠有默認的方法實現。接口在 jdk1.8 以前沒有方法實現,1.8 以後可使用 default 關鍵字定義方法實現
  • 抽象類能夠有構造函數,接口不能夠
  • 子類使用 extends 關鍵字來繼承抽象類。若是子類不是抽象類的話,它須要提供抽象類中全部聲明的方法的實現。子類使用關鍵字 implements 來實現接口。它須要提供接口中全部聲明的方法的實現
  • 抽象方法能夠有 publicprotecteddefault 這些修飾符,接口方法默認是 public 的,能夠缺省
  • 抽象類的字段可使用任何修飾符。接口中字段默認是 public final
  • 單繼承 多實現
  • 抽象類:is-a 的關係,體現的是一種關係的延續。 接口: like-a 體現的是一種功能的擴展關係

3. Iterator 和 Enumeration 的區別 ?

  • 函數接口不一樣 Enumeration 只有 2 個函數接口。經過 Enumeration,咱們只能讀取集合的數據,而不能對數據進行修改。

    Iterator 有 3 個函數接口。Iterator 除了能讀取集合的數據以外,也能數據進行刪除操做。
    複製代碼
  • Iterator 支持 fail-fast 機制,而 Enumeration 不支持。 Enumeration 是 JDK 1.0 添加的接口。使用到它的函數包括 Vector 、Hashtable 等類,這些類都是 JDK 1.0 中加入的,Enumeration 存在的目的就是爲它們提供遍歷接口。Enumeration 自己並無支持同步,而在 Vector 、Hashtable 實現 Enumeration 時,添加了同步。

    而 Iterator 是 JDK 1.2 才添加的接口,它也是爲了 HashMap 、ArrayList 等集合提供遍歷接口。Iterator 是支持 fail-fast 機制的:當多個線程對同一個集合的內容進行操做時,就可能會產生 fail-fast 事件。
      
      因此 Enumeration 比 Iterator 的遍歷速度更快。
    複製代碼

4. 你贊成 組合優先於繼承 嗎 ?

繼承的功能很是強大,可是也存在諸多問題,由於它違背了封裝原則 。 只 有當子類和超類之間確實存在子類型關係時,使用繼承纔是恰當的 。 即便如此,若是子 類和超類處在不一樣的包中,而且超類並非爲了繼承而設計的,那麼繼承將會致使脆弱性 ( fragility ) 。 爲了不這種脆弱性,能夠用複合和轉發機制來代替繼承,尤爲是當存在適當 的接口能夠實現包裝類的時候 。 包裝類不只比子類更加健壯,並且功能也更增強大。(也就是裝飾者模式)。

具體見 Effective Java 18條 複合優先於繼承

5. 方法重載和方法重寫的區別 ?

同一個類中,方法名稱相同可是參數類型不一樣,稱爲方法重載。 重載的方法在編譯過程當中便可完成識別。具體到每個方法調用,Java 編譯器會根據所傳入參數的聲明類型(注意與實際類型區分)來選取重載方法。

若是子類中定義了與父類中非私有方法同名的方法,並且這兩個方法參數類型不一樣,那麼在子類中,這兩個方法一樣構成了重載。反之,若是方法參數類型相同, 這時候要區分是不是靜態方法。若是是靜態方法,那麼子類中的方法會隱藏父類的方法。若是不是靜態方法,就是子類重寫了父類的方法、

對重載方法的區分在編譯階段已經完成,重載也被稱爲靜態綁定,或者編譯時多態。重寫被稱爲爲動態綁定。

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

訪問級別 訪問控制修飾符 同類 同包 子類 不一樣的包
公開 public
受保護 protected --
默認 沒有訪問控制修飾符 -- --
私有 private -- -- --

7. 一個接口能夠實現另外一個接口嗎 ?

能夠,可是不是 implements , 而是 extends 。一個接口能夠繼承一個或多個接口。

8. 什麼是多態 ?什麼是繼承 ?

在 java 中多態有編譯期多態(靜態綁定)和運行時多態(動態綁定)。方法重載是編譯期多態的一種形式。方法重寫是運行時多態的一種形式。

多態的另外一個重要例子是父類引用子類實例。事實上,知足 is-a 關係的對象均可以看出多態。 例如,Cat 類 是 Animal 類的子類,因此 Cat is Animal,這就知足了 is-a 關係。

繼承性(Inheritance)是指,在某種狀況下,一個類會有「子類」。子類比本來的類(稱爲父類)要更加具體化。例如,「狗」這個類可能會有它的子類「牧羊犬」和「吉娃娃犬」。 子類會繼承父類的屬性和行爲,而且也可包含它們本身的。這意味着程序員只須要將相同的代碼寫一次。

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

在 java 中一個類不能夠繼承多個類,可是接口能夠繼承多個接口。

10. 什麼是設計模式?

設計模式就不在這裏展開說了。推薦一個 github 項目 java-design-patterns。 後面有機會單獨寫一寫設計模式。

集合和泛型

11. Arrays vs ArrayLists

Arrays 是一個工具類,提供了許多操做,排序,查找數組的靜態方法。

ArrayList 是一個動態數組隊列,實現了 Collection 和 List 接口,提供了數據的增長,刪除,獲取等方法。

12. HashSet vs TreeSet

HashSetTreeSet 都是基於 Set 接口的實現類。其中 TreeSetSet 的子接口 SortedSet 的實現類。

HashSet 基於哈希表實現,它不保證集合的迭代順序,特別是它不保證該順序恆久不變。容許 null 值。不支持同步。

TreeSet 基於二叉樹實現,它的元素自動排序,按照天然順序或者提供的比較器進行排序,因此 TreeSet 中元素要實現 Comparable 接口。不容許 null 值。

13. HashMap vs HashSet

HashMap HashSet
實現了 Map 接口 實現了 Set 接口
存儲鍵值對 僅存儲對象
調用 put() 向 map 中添加元素 調用 add() 方法向 Set中 添加元素
使用鍵對象來計算 hashcode 值 使用成員對象來計算 hashcode 值,對於兩個對象來講 hashcode 可能相同,因此 equals() 方法用來判斷對象的相等性,若是兩個對象不一樣的話,那麼返回false
HashMap 相對於 HashSet 較快,由於它是使用惟一的鍵獲取對象 HashSet 較 HashMap 來講比較慢

14. Stack vs Queue

隊列是一種基於先進先出(FIFO)策略的集合類型。隊列在保存元素的同時保存它們的相對順序:使它們入列順序和出列順序相同。隊列在生活和編程中極其常見,就像排隊,先進入隊伍的老是先出去。

棧是一種基於後進先出(LIFO)策略的集合類型,當使用 foreach 語句遍歷棧中的元素時,元素的處理順序和它們被壓入的順序正好相反。就像咱們的郵箱,後進來的郵件老是會先看到。

15. 解釋 java 中的泛型

16. String 類是如何實現的?它爲何被設計成不可變類 ?

String 類是使用 char 數組實現的,jdk 9 中改成使用 byte 數組實現。 不可變類好處:

  • 不可變類比較簡單。
  • 不可變對象本質上是線程安全的,它們不要求同步。不可變對象能夠被自由地共享。
  • 不只能夠共享不可變對象,甚至能夠共享它們的內部信息。
  • 不可變對象爲其餘對象提供了大量的構建。
  • 不可變類真正惟一的缺點是,對於每一個不一樣的值都須要一個單獨的對象。

走進 JDK 之 String

對象和基本類型

17. 爲何說 String 不可變 ?

  • Stringfinal 類,不能夠被擴展
  • private final char value[],不可變
  • 沒有對外提供任何修改 value[] 的方法

參見個人文章 String 爲何不可變 ?

18. 什麼是 String.intern() ? 什麼時候使用? 爲何使用 ?

若是常量池中存在當前字符串, 就會直接返回當前字符串. 若是常量池中沒有此字符串, 會將此字符串放入常量池中後, 再返回。

將運行時須要大量使用的字符串放入常量池。

深刻解析 String.intern()

19. 列舉 8 種基本類型

基本類型 大小 最大值 最小值 包裝類 虛擬機中符號
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 區別

int 是基本數據類型,通常直接存儲在棧中,更加高效

Integer 是包裝類型,new 出來的對象存儲在堆中,比較耗費資源

21. 什麼是自動裝箱拆箱 ?

把基本數據類型轉換成包裝類的過程叫作裝箱。

把包裝類轉換成基本數據類型的過程叫作拆箱。

在Java 1.5以前,要手動進行裝箱,

Integer i = new Integer(10);
複製代碼

java 1.5 中,提供了自動拆箱與自動裝箱功能。須要拆箱和裝箱的時候,會自動進行轉換。

Integer i =10;  //自動裝箱
int b= i;     //自動拆箱
複製代碼

自動裝箱都是經過Integer.valueOf()方法來實現的,Integer的自動拆箱都是經過integer.intValue來實現的。

關於 Java 基本類型能夠看個人一篇總結文章:走進 JDK 之談談基本類型

22. Java 中的類型轉換

賦值和方法調用轉換規則:從低位類型到高位類型自動轉換;從高位類型到低位類型須要強制類型轉換:

  1. 布爾型和其它基本數據類型之間不能相互轉換;
  2. byte 型能夠轉換爲 shortintlongfloatdouble
  3. short 可轉換爲 intlongfloatdouble
  4. char 可轉換爲 intlongfloatdouble
  5. int 可轉換爲 longfloatdouble
  6. long 可轉換爲 floatdouble
  7. float 可轉換爲 double

基本類型 與 對應包裝類 可自動轉換,這是自動裝箱和折箱的原理。 

兩個引用類型間轉換:

  1. 子類能直接轉換爲父類 或 接口類型

  2. 父類轉換爲子類要強制類型轉換,且在運行時若實際不是對應的對象,會拋出 ClassCastException 運行時異常;

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

值傳遞。

值傳遞(pass by value)是指在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中若是對參數進行修改,將不會影響到實際參數。

引用傳遞(pass by reference)是指在調用函數時將實際參數的地址直接傳遞到函數中,那麼在函數中對參數所進行的修改,將影響到實際參數。

Java 調用方法傳遞的是實參引用的副本。

爲何說Java中只有值傳遞。

24. 對象實例化和初始化之間的區別 ?

Initialization(實例化) 是建立新對象而且分配內存的過程。新建立的變量必須顯示賦值,不然它將使用存儲在該內存區域上的上一個變量包含的值。爲了不這個問題,Java 會給不一樣的數據類型賦予默認值:

  • 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(初始化)是給已經聲明的變量顯示賦值的過程。

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

25. 局部變量、實例變量以及類變量之間的區別?

局部變量僅僅存在於建立它的方法中,他們被保存在棧內存,在方法外沒法得到它們的引用。Java 的方法執行不是依賴寄存器的,而是棧幀,每一個方法的執行和結束都伴隨着棧幀的入棧和出棧,也伴隨着局部變量的建立和釋放。

實例變量也就是成員變量,聲明在類中,依賴類實例而存在,不一樣類實例中變量值也可能不一樣。

類變量也就是靜態變量,在全部類實例中只有一個值,在一個地方改變它的值將會改變全部類實例中的值。

Java 內存模型和垃圾收集器

26. 什麼是垃圾收集器 ? 它是如何工做的 ?

Java 和 C++ 以前有一堵由內存動態分配和垃圾收集技術所圍成的高牆,牆外面的人想進去,牆裏面的人想出去。

垃圾收集器主要用來回收堆上的無用對象,Java 開發者只管建立和使用對象,JVM 來爲你自動分配和回收內存。

JVM 經過可達性分析算法來斷定對象是否存活。這個算法的基本思路就是經過一系列的稱爲 GC Roots 的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到 GC Roots 沒有任何引用鏈相連(用圖論的話來講,就是從 GC Roots 到這個對象不可達)時,則證實此對象是不可用的。

即便在可達性分析算法中不可達的對象,也並不是是 非死不可 的,這時候他們暫時處於緩刑階段,要真正宣告一個對象死亡,至少要經歷兩次標記過程。

更多詳細內容能夠閱讀 《深刻理解 Java 虛擬機》 第三章 垃圾收集器與內存分配策略

27. 什麼是 java 內存模型? 它遵循了什麼原則?它的堆棧是如何組織的 ?

Java 虛擬機規範中試圖定義一種 Java 內存模型(Java Memory Model,JMM)來屏蔽掉各類硬件和操做系統的內存訪問差別,以實現讓 Java 程序在各類平臺下都能達到一致的內存訪問效果。JMM 是語言級的內存模型,它確保在不一樣的編譯器和不一樣的處理器平臺上,經過禁止特定類型的編譯器重排序和處理器重排序,爲程序員提供一致的內存可見性保證。

JMM 內存模型的抽象表示以下:

結合上圖,在 Java 中,全部實例域、靜態域和數組元素都存儲在堆內存中,堆內存在線程之間共享。局部變量、方法定義參數和異常處理器參數在棧中,不會在線程之間共享,它們不會有內存可見性問題,也不會受內存模型影響。

Java 線程之間的通訊由 JMM 控制,JMM 決定一個線程對共享變量的寫入什麼時候對另外一個線程可見。JMM 經過控制主內存與每一個線程的本地內存之間的交互,來爲 Java 程序員提供內存可見性保證。

更多詳細內容能夠閱讀 《Java 併發編程的藝術》

28. 什麼是 內存泄漏,java 如何處理它 ?

內存泄露就是不會再被使用的對象沒法被 GC 回收,即這些對象在可達性分析中是可達的,但在程序中的確不會再被使用。好比長生命週期的對象引用了短生命週期的對象,致使短生命週期對象不能被回收。

Java 應該不會處理內存泄漏,咱們能作的更可能是防患於未然,以及使用合理手段監測,好比 Android 裏經常使用的 LeakCanary,詳細原理能夠看我以前的一篇文章 LeakCanary 源碼解析

29. 什麼是 強引用,軟引用,弱引用,虛引用 ?

在 JDK 1.2 以後,Java 對引用的概念進行了擴充,將引用分爲 強引用、軟引用、弱引用、虛引用 4 種,這 4 種引用強度依次逐漸減弱。

  • 強引用就是指程序代碼之中廣泛存在的,相似 Object obj = new Object() 這類的引用,只要強引用還存在,GC 永遠不會回收掉被引用的對象。

  • 軟引用是用來描述一些還有用但並不是必須的對象。對於軟引用關聯着的對象,在系統將要發生內存溢出異常以前,將會把這些對象列進回收範圍之中進行第二次回收。若是此次回收尚未足夠的內存,纔會拋出內存溢出異常。在 JDK 1.2 以後,提供了 SoftReference 類來實現軟引用。

  • 弱引用也是用來描述非必須對象的,但它的強度比軟引用要弱一些,被弱引用關聯的對象只能生存到下一次 GC 發生以前。當 GC 工做時,沒法當前內存是否足夠,都會回收掉只被弱引用關聯的對象。在 JDK 1.2 以後,提供了 WeakReference 類來實現弱引用。

  • 虛引用也稱爲幽靈引用或者幻影引用,它是最弱的一種引用關係。一個對象是否有虛引用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被 GC 回收時收到一個系統通知。在 JDK 1.2 以後,提供了 PhantomReference 類來實現虛引用。

併發

30. 關鍵字 synchronized 的做用 ?

關鍵字 synchronized 能夠修飾方法或者以同步塊的形式來進行使用,它主要確保多個線程在同一時刻,只能有一個線程處於方法或同步塊中,它保證了線程對變量訪問的可見性和排他性。

  • 對於普通同步方法,鎖是當前實例對象
  • 對於靜態同步方法,鎖是當前類的 Class 對象
  • 對於同步方法塊,鎖是 Synchonized 括號裏配置的對象

31. ThreadPoolExecutor 做用 ?

在 Java 中,使用線程來執行異步任務。Java 線程的建立與銷燬須要必定的開銷,若是咱們爲每個任務都建立一個新線程來執行,這些線程的建立與銷燬將消耗大量的計算資源。同時,爲每個任務建立一個新線程來執行,這種策略可能會使處於高負荷的應用最終崩潰。

關於線程池的詳細介紹,推薦一篇文章 Java併發編程:線程池的使用

32. 關鍵字 volatile 的做用 ?

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

可是,過多的使用 volatile 是沒必要要的,由於它會下降程序執行的效率。

異常

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

try 代碼塊用來標記須要進行異常監控的代碼

catch 代碼塊跟在 try 代碼塊以後,用來捕獲 try 代碼塊中觸發的某種指定類型的異常。除了聲明所捕獲的異常類型以外,catch 代碼塊還定義了針對該異常類型的異常處理器。在 Java 中,try 代碼塊後面能夠跟着多個 catch 代碼塊,來捕獲不一樣的異常。Java 虛擬機會從上至下匹配異常處理器。所以,前面的 catch 代碼塊所捕獲的異常類型不能覆蓋後面的,不然編譯器會報錯。

finally 代碼塊跟在 try 代碼塊和 catch 代碼塊以後,用來聲明一段一定運行的代碼。它的設計初衷是爲了不跳過某些關鍵的清理代碼,例如關閉已打開的系統資源。

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

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

finally 代碼塊的編譯比較複雜。當前版本 Java 編譯器的作法,是複製 finally 代碼塊的內容,分別放在try-catch 代碼塊全部正常執行路徑以及異常執行路徑的出口中。

以上內容來自極客時間專欄 深刻拆解 Java 虛擬機

34. Checked Exception 和 Un-Checked Exception 區別 ?

在 Java 中,全部異常都是 Throwable 類或者其子類的實例。Throwable 有兩大直接子類。一個是 Error,涵蓋程序不該捕獲的異常。當 Error 發生時,它的執行狀態已經沒法恢復,須要終止線程甚至虛擬機。第二個子類是 Exception,涵蓋程序可能須要捕獲而且處理的異常。

Exception 有一個特殊的子類 RuntimeException,運行時異常,用來表示 「程序雖然沒法繼續執行,但還能搶救一下」 的狀況。

RuntimeException 和 Error 屬於 Java 裏的非檢查異常(unchecked exception)。其餘異常則屬於檢查異常(checked exception)。在 Java 語法中,全部的檢查異常都須要程序顯式地捕獲,或者在方法聲明中用 throws 關鍵字標註。一般狀況下,程序中自定義的異常應爲檢查異常,以便最大化利用 Java 編譯器的編譯時檢查。

以上內容來自極客時間專欄 深刻拆解 Java 虛擬機

其餘

35. 什麼是序列化?如何實現 ?

序列化是將對象轉換成字節流以便持久化存儲的過程。它能夠保存對象的狀態和數據,方便在特定時刻從新構建該對象。在 Android 中,通常使用 Serializable , Externalizable (implements Serializable) 或者 Parcelable 接口。

Serializable 最容易實現,直接實現接口便可。Externalizable 能夠在序列化的過程當中插入一些本身的邏輯代碼,考慮到它是 Java 早期版本的遺留物,如今基本已經沒人再使用它。在 Android 中推薦使用 Parcelable ,它就是爲 Android 而實現,性能是 Serializable 的十倍,由於 Serializable 使用了反射。反射不只慢,還會建立大量臨時對象,致使頻繁 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. 關鍵字 transient 的做用 ?

transient 很簡單,它的做用就是讓被其修飾的成員變量在序列化的過程當中不被序列化。

37. 什麼是匿名內部類 ?

匿名內部類是惟一一種沒有構造器的類。正由於其沒有構造器,因此匿名內部類的使用範圍很是有限,大部分匿名內部類用於接口回調。匿名內部類在編譯的時候由系統自動起名爲 Outter$1.class。通常來講,匿名內部類用於繼承其餘類或是實現接口,並不須要增長額外的方法,只是對繼承方法的實現或是重寫。

Android 中應用最多見的就是各類點擊事件。

38. 對象的 == 和 .equals 區別 ?

對於對象而言,== 永遠比較的都是其內存地址。而 equals() 則要看該對象是否重寫了 equals() 方法,若是沒有則會調用父類的 equals() 方法,若是父類也沒有實現的話,就不斷向上追溯,直至 Object 類。看一下 Object.java 中的 equals() 方法:

public boolean equals(Object obj) {
    return (this == obj);
}
複製代碼

Object 中,equals 等同於 ==,都是比較內存地址。再看一下不是比較內存地址的,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;
    }
複製代碼

這裏比較的就再也不是內存地址,而是其實際的值。

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

equals() 用於判斷兩個對象是否相等,未被重寫的話就是判斷內存地址,和 == 語義一致。重寫了的話,就按照重寫的邏輯進行判斷。

hashCode() 用於計算對象的哈希碼,默認實現是將對象的內存地址做爲哈希碼返回,能夠保證不一樣對象的返回值不一樣。理論上,hashCode 也能夠用來比較對象是否相等。hashCode() 主要用在哈希表中,好比 HashMapHashSet 等。

當咱們向哈希表(如HashSet、HashMap等)中添加對象object時,首先調用hashCode()方法計算object的哈希碼,經過哈希碼能夠直接定位object在哈希表中的位置(通常是哈希碼對哈希表大小取餘)。若是該位置沒有對象,能夠直接將object插入該位置;若是該位置有對象(可能有多個,經過鏈表實現),則調用equals()方法比較這些對象與object是否相等,若是相等,則不須要保存object;若是不相等,則將該對象加入到鏈表中。

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

40. 構造函數中爲何不能調用抽象方法 ?

構造函數中不能調用抽象方法,說的更嚴謹一點,構造函數中不能調用可被覆蓋的方法

先看這樣一個例子:

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();
    }

}
複製代碼

最後的打印結果:

null
2019-04-01T02:42:13.947Z
複製代碼

第一次打印出的是 null,由於 overrideMe 方法被 Super 構造器調用的時候,構造器 Sub 尚未機會初始化 instant 域 。 注意,這個程序觀察到的 final 域處於兩種不一樣的狀態 。

超類的構造器在子類的構造器以前運行,因此,子類中覆蓋版本的方法將會在子類的構造器運行以前先被 調用 。 若是該覆蓋版本的方法依賴於子類構造器所執行的任何初始化工做,該方法將不會如預期般執行 。

41. 你何時會使用 final 關鍵字 ?

對於一個 final 變量,若是是基本數據類型的變量,則其數值一旦在初始化以後便不能更改;若是是引用類型的變量,則在對其初始化以後便不能再讓其指向另外一個對象。

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

final 變量是基本數據類型以及 String 類型時,若是在編譯期間能知道它的確切值,則編譯器會把它當作編譯期常量使用。

final 修飾方法參數也是爲了強調參數不可改變。

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

淺析 Java 的 final 關鍵字

42. final, finally 和 finalize 的區別 ?

final 和 finally 就再也不說了。重點看看 finalize。

若是類中重寫了 finalize 方法,當該類對象被回收時,finalize 方法有可能會被觸發。 Effective Java 中明確說明 終結方法(finalize)一般是不可預測的,也是很危險的,通常狀況下是沒必要要的。

JVM 不只不保證 finalize 方法能夠被及時執行,並且根本就不保證它們會被執行。因此不要依賴 finalize 方法來作一些例如 釋放資源的操做。可能會延時對象的回收,形成性能損失。

43. Java 中 static 關鍵字的含義 ?

static 就是爲了方便在沒有建立對象的狀況下來進行調用(方法/變量)。

  • static 方法通常稱做靜態方法,因爲靜態方法不依賴於任何對象就能夠進行訪問,所以對於靜態方法來講,是沒有 this 的,由於它不依附於任何對象,既然都沒有對象,就談不上 this 了。而且因爲這個特性,在靜態方法中不能訪問類的非靜態成員變量和非靜態成員方法,由於非靜態成員方法/變量都是必須依賴具體的對象纔可以被調用。

  • static 變量也稱做靜態變量,靜態變量和非靜態變量的區別是:靜態變量被全部的對象所共享,在內存中只有一個副本,它當且僅當在類初次加載時會被初始化。而非靜態變量是對象所擁有的,在建立對象的時候被初始化,存在多個副本,各個對象擁有的副本互不影響。

  • static 關鍵字還有一個比較關鍵的做用就是 用來造成靜態代碼塊以優化程序性能。static 塊能夠置於類中的任何地方,類中能夠有多個 static 塊。在類初次被加載的時候,會按照 static 塊的順序來執行每一個 static 塊,只會在類加載的時候執行一次。

static 成員變量的初始化順序按照定義的順序進行初始化。

44. 靜態方法能夠重寫嗎 ?

你能夠重寫,但這並非多態的體現,並非真正意義上的重寫。子類的靜態方法會隱藏父類的靜態方法,這兩個方法並無什麼關係,具體調用哪個方法是看調用者是哪一個對象的引用,並不存在多態。只有普通的方法調用才能夠是多態的。

45. 靜態代碼塊如何運行 ?

靜態代碼塊隨着類的加載而執行,並且只執行一次。

靜態代碼塊通過編譯後是放在 <clinit> 中, <clinit> 在jvm第一次加載class文件時調用,包括靜態變量初始化語句和靜態塊的執行。

46. 什麼是反射 ?

反射 (Reflection) 是 Java 的特徵之一,它容許運行中的 Java 程序獲取自身的信息,而且能夠操做類或對象的內部屬性。

Oracle 官方對反射的解釋是:

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.

簡而言之,經過反射,咱們能夠在運行時得到程序或程序集中每個類型的成員和成員的信息。程序中通常的對象的類型都是在編譯期就肯定下來的,而 Java 反射機制能夠動態地建立對象並調用其屬性,這樣的對象的類型在編譯期是未知的。因此咱們能夠經過反射機制直接建立對象,即便這個對象的類型在編譯期是未知的。

反射的核心是 JVM 在運行時才動態加載類或調用方法/訪問屬性,它不須要事先(寫代碼的時候或編譯期)知道運行對象是誰。

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

在運行時判斷任意一個對象所屬的類; 在運行時構造任意一個類的對象; 在運行時判斷任意一個類所具備的成員變量和方法(經過反射甚至能夠調用private方法); 在運行時調用任意一個對象的方法

深刻 Java 反射

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

47. 什麼是依賴注入 ?列舉幾個庫 ?你使用過嗎 ?

理解的還不夠透徹,放上來一篇網上的寫的不錯的文章: 輕鬆理解 Java開發中的依賴注入(DI)和控制反轉(IOC)

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

StringBuilder 內部維護了一個可變長的 char[],用來存儲和拼接字符串,從而避免了因 String 是不可變類帶來的頻繁建立 String 對象的問題。

49. StringBuffer 和 StringBuilder 區別 ?

StringBufferStringBuilder 在使用上基本沒有區別。StringBuffer 經過 synchronized 關鍵字保證了線程安全,而 StringBuilder 沒有任何同步操做。因此在肯定無線程同步問題時,使用 StringBuilder 效率更高。

50. Enumeration and an Iterator 區別 ?

重複了,見第 3 題。

51. fail-fast and fail-safe 區別 ?

fail-fast 機制在遍歷一個集合時,當集合結構被修改,會拋出 Concurrent Modification Exception。迭代器在遍歷過程當中是直接訪問內部數據的,所以內部的數據在遍歷的過程當中沒法被修。 爲了保證不被修改,迭代器內部維護了一個標記 「mode」 ,當集合結構改變(添加刪除或者修改),標記 "mode" 會被修改, 而迭代器每次的 hasNext()next() 方法都會檢查該 "mode" 是否被改變,當檢測到被修改時,拋出 Concurrent Modification Exception

fail-safe 任何對集合結構的修改都會在一個複製的集合上進行修改,所以不會拋出 ConcurrentModificationException

fail-safe 機制有兩個問題:

  1. 須要複製集合,產生大量的無效對象,開銷大

  2. 沒法保證讀取的數據是目前原始數據結構中的數據

52. 什麼是 NIO ?

在 JDK 1. 4 中 新 加入 了 NIO( New Input/ Output) 類, 引入了一種基於通道和緩衝區的 I/O 方式, 它可使用 Native 函數庫直接分配堆外內存,而後經過一個存儲在 Java 堆的 DirectByteBuffer 對象做爲這塊內存的引用進行操做, 避免了在 Java 堆和 Native 堆中來回複製數據。

NIO 是一種同步非阻塞的 IO 模型。同步是指線程不斷輪詢 IO 事件是否就緒,非阻塞是指線程在等待 IO 的時候,能夠同時作其餘任務。 同步的核心就是 Selector,Selector 代替了線程自己輪詢 IO 事件,避免了阻塞同時減小了沒必要要的線程消耗;非阻塞的核心就是通道和緩衝區, 當 IO 事件就緒時,能夠經過寫道緩衝區,保證 IO 的成功,而無需線程阻塞式地等待。

深刻理解 Java NIO

End

文章首發於微信公衆號: 秉心說 , 專一 Java 、 Android 原創知識分享,LeetCode 題解,歡迎關注!

微信搜索 秉心說, 或者掃碼關注,回覆 Core Java 便可領取全部回答 pdf 文檔 。

相關文章
相關標籤/搜索