1. 程式人生 > >6 道 BATJ 必考的 Java 面試題

6 道 BATJ 必考的 Java 面試題

題目一

請對比 Exception 和 Error,另外,執行時異常與一般異常有什麼區別?

考點分析:

分析 Exception 和 Error 的區別,是從概念角度考察了 Java 處理機制。總的來說,還處於理解的層面,面試者只要闡述清楚就好了。

我們在日常程式設計中,如何處理好異常是比較考驗功底的,我覺得需要掌握兩個方面。

第一,理解 Throwable、Exception、Error 的設計和分類。 比如,掌握那些應用最為廣泛的子類,以及如何自定義異常等。

很多面試官會進一步追問一些細節,比如,你瞭解哪些 Error、Exception 或者 RuntimeException?我畫了一個簡單的類圖,並列出來典型例子,可以給你作為參考,至少做到基本心裡有數。

第二,理解 Java 語言中操作 Throwable 的元素和實踐。 掌握最基本的語法是必須的,如 try-catch-finally 塊,throw、throws 關鍵字等。與此同時,也要懂得如何處理典型場景。

典型回答:

Exception 和 Error 都是繼承了 Throwable 類,在 Java 中只有 Throwable 類的例項才可以被丟擲(throw)或者捕獲(catch),它是異常處理機制的基本組成型別。

Exception 和 Error 體現了 Java 平臺設計者對不同異常情況的分類。Exception 是程式正常執行中,可以預料的意外情況,可能並且應該被捕獲,進行相應處理。點選

這裡看一下壯烈犧牲的阿里巴巴面試經驗。

Error 是指在正常情況下,不大可能出現的情況,絕大部分的 Error 都會導致程式(比如 JVM 自身)處於非正常的、不可恢復狀態。既然是非正常情況,所以不便於也不需要捕獲,常見的比如 OutOfMemoryError 之類,都是 Error 的子類。

Exception 又分為可檢查(checked)異常和不檢查(unchecked)異常,可檢查異常在原始碼裡必須顯式地進行捕獲處理,這是編譯期檢查的一部分。前面我介紹的不可查的 Error,是 Throwable 不是 Exception。

不檢查異常就是所謂的執行時異常,類似 NullPointerException、ArrayIndexOutOfBoundsException 之類,通常是可以編碼避免的邏輯錯誤,具體根據需要來判斷是否需要捕獲,並不會在編譯期強制要求。

題目二

談談 Java 反射機制,動態代理是基於什麼原理?

考點分析:

這個題目給我的第一印象是稍微有點誘導的嫌疑,可能會下意識地以為動態代理就是利用反射機制實現的,這麼說也不算錯但稍微有些不全面。功能才是目的,實現的方法有很多。

總的來說,這道題目考察的是 Java 語言的另外一種基礎機制: 反射,它就像是一種魔法,引入執行時自省能力,賦予了 Java 語言令人意外的活力,通過執行時操作元資料或物件,Java 可以靈活地操作執行時才能確定的資訊。 而動態代理,則是延伸出來的一種廣泛應用於產品開發中的技術,很多繁瑣的重複程式設計,都可以被動態代理機制優雅地解決。

從考察知識點的角度,這道題涉及的知識點比較龐雜,所以面試官能夠擴充套件或者深挖的內容非常多,比如:

  • 考察你對反射機制的瞭解和掌握程度。

  • 動態代理解決了什麼問題,在你業務系統中的應用場景是什麼?

  • JDK 動態代理在設計和實現上與 cglib 等方式有什麼不同,進而如何取捨?

典型回答

反射機制是 Java 語言提供的一種基礎功能,賦予程式在執行時自省(introspect,官方用語)的能力。通過反射我們可以直接操作類或者物件,比如獲取某個物件的類定義,獲取類宣告的屬性和方法,呼叫方法或者構造物件,甚至可以執行時修改類定義。

動態代理是一種方便執行時動態構建代理、動態處理代理方法呼叫的機制,很多場景都是利用類似機制做到的,比如用來包裝 RPC 呼叫、面向切面的程式設計(AOP)。

實現動態代理的方式很多,比如 JDK 自身提供的動態代理,就是主要利用了上面提到的反射機制。還有其他的實現方式,比如利用傳說中更高效能的位元組碼操作機制,類似 ASM、cglib(基於 ASM)、Javassist 等。

題目三

Java 提供了哪些 IO 方式? NIO 如何實現多路複用?

考點分析

在實際面試中,從傳統 IO 到 NIO、NIO 2,其中有很多地方可以擴充套件開來,考察點涉及方方面面,比如:

  • 基礎 API 功能與設計, InputStream/OutputStream 和 Reader/Writer 的關係和區別。

  • NIO、NIO 2 的基本組成。

  • 給定場景,分別用不同模型實現,分析 BIO、NIO 等模式的設計和實現原理。

  • NIO 提供的高效能資料操作方式是基於什麼原理,如何使用?

  • 或者,從開發者的角度來看,你覺得 NIO 自身實現存在哪些問題?有什麼改進的想法嗎?

典型回答

Java IO 方式有很多種,基於不同的 IO 抽象模型和互動方式,可以進行簡單區分。

首先,傳統的 java.io 包,它基於流模型實現,提供了我們最熟知的一些 IO 功能,比如 File 抽象、輸入輸出流等。互動方式是同步、阻塞的方式,也就是說,在讀取輸入流或者寫入輸出流時,在讀、寫動作完成之前,執行緒會一直阻塞在那裡,它們之間的呼叫是可靠的線性順序。點選這裡看一下壯烈犧牲的阿里巴巴面試經驗。

java.io 包的好處是程式碼比較簡單、直觀,缺點則是 IO 效率和擴充套件性存在侷限性,容易成為應用效能的瓶頸。

很多時候,人們也把 java.net 下面提供的部分網路 API,比如 Socket、ServerSocket、HttpURLConnection 也歸類到同步阻塞 IO 類庫,因為網路通訊同樣是 IO 行為。

第二,在 Java 1.4 中引入了 NIO 框架(java.nio 包),提供了 Channel、Selector、Buffer 等新的抽象,可以構建多路複用的、同步非阻塞 IO 程式,同時提供了更接近作業系統底層的高效能資料操作方式。點選這裡看一下壯烈犧牲的阿里巴巴面試經驗。

第三,在 Java 7 中,NIO 有了進一步的改進,也就是 NIO 2,引入了非同步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。非同步 IO 操作基於事件和回撥機制,可以簡單理解為,應用操作直接返回,而不會阻塞在那裡,當後臺處理完成,作業系統會通知相應執行緒進行後續工作。

題目四

如何保證容器是執行緒安全的?ConcurrentHashMap 如何實現高效地執行緒安全?

典型回答:

Java 提供了不同層面的執行緒安全支援。在傳統集合框架內部,除了 Hashtable 等同步容器,還提供了所謂的同步包裝器(Synchronized Wrapper),我們可以呼叫 Collections 工具類提供的包裝方法,來獲取一個同步的包裝容器(如 Collections.synchronizedMap),但是它們都是利用非常粗粒度的同步方式,在高併發情況下,效能比較低下。點選這裡看一下壯烈犧牲的阿里巴巴面試經驗

另外,更加普遍的選擇是利用併發包提供的執行緒安全容器類,它提供了:

  • 各種併發容器,比如 ConcurrentHashMap、CopyOnWriteArrayList。

  • 各種執行緒安全佇列(Queue/Deque),如 ArrayBlockingQueue、SynchronousQueue。

  • 各種有序容器的執行緒安全版本等。

具體保證執行緒安全的方式,包括有從簡單的 synchronize 方式,到基於更加精細化的,比如基於分離鎖實現的 ConcurrentHashMap 等併發實現等。具體選擇要看開發的場景需求,總體來說,併發包內提供的容器通用場景,遠優於早期的簡單同步實現。

題目五

談談介面和抽象類有什麼區別?

考點分析:

這是個非常高頻的 Java 面向物件基礎問題,看起來非常簡單的問題,如果面試官稍微深入一些,你會發現很多有意思的地方,可以從不同角度全面地考察你對基本機制的理解和掌握。

比如:

  • 對於 Java 的基本元素的語法是否理解準確。 能否定義出語法基本正確的介面、抽象類或者相關繼承實現,涉及過載(Overload)、重寫(Override)更是有各種不同的題目。

  • 在軟體設計開發中妥善地使用介面和抽象類。 你至少知道典型應用場景,掌握基礎類庫重要介面的使用;掌握設計方法,能夠在 review 程式碼的時候看出明顯的不利於未來維護的設計。

  • 掌握 Java 語言特性演進。 現在非常多的框架已經是基於 Java 8,並逐漸支援更新版本,掌握相關語法,理解設計目的是很有必要的。

典型回答:

1.繼承抽象類的子類們的本質都是相似的,它們體現的是一種 “is-a" 的關係,就像動物中的貓和狗;而對於介面的繼承更多的是一種行為的相似,是一種 “like-a” 的關係,比如飛機和鳥,它們都具有飛的行為,卻並不需要在本質上相似。

2.抽象類可以擁有任意範圍的成員資料,既可以是抽象,也可以是非抽象;但是介面,所有的方法必須是抽象的,所有的成員變數必須是 public static final的,某種程度上來說,介面是對抽象類的一種抽象。

3.一個類只能繼承一個抽象類,但卻可以實現多個介面。

題目六

synchronized 和 ReentrantLock 有什麼區別?有人說 synchronized 最慢,這話靠譜嗎?

考點分析:

題目是考察併發程式設計的常見基礎題,下面給出的典型回答算是一個相對全面的總結。

對於併發程式設計,不同公司或者面試官面試風格也不一樣,有個別大廠喜歡一直追問你相關機制的擴充套件或者底層,有的喜歡從實用角度出發,所以你在準備併發程式設計方面需要一定的耐心。

個人認為,鎖作為併發的基礎工具之一,你至少需要掌握:

  • 理解什麼是執行緒安全。

  • synchronized、ReentrantLock 等機制的基本使用與案例。

更進一步,你還需要:

  • 掌握 synchronized、ReentrantLock 底層實現;理解鎖膨脹、降級;理解偏斜鎖、自旋鎖、輕量級鎖、重量級鎖等概念。

  • 掌握併發包中 java.util.concurrent.lock 各種不同實現和案例分析。

典型回答:

synchronized 是 Java 內建的同步機制,所以也有人稱其為 Intrinsic Locking,它提供了互斥的語義和可見性,當一個執行緒已經獲取當前鎖時,其他試圖獲取的執行緒只能等待或者阻塞在那裡。

在 Java 5 以前,synchronized 是僅有的同步手段,在程式碼中, synchronized 可以用來修飾方法,也可以使用在特定的程式碼塊兒上,本質上 synchronized 方法等同於把方法全部語句用 synchronized 塊包起來。

ReentrantLock,通常翻譯為再入鎖,是 Java 5 提供的鎖實現,它的語義和 synchronized 基本相同。再入鎖通過程式碼直接呼叫 lock() 方法獲取,程式碼書寫也更加靈活。與此同時,ReentrantLock 提供了很多實用的方法,能夠實現很多 synchronized 無法做到的細節控制,比如可以控制 fairness,也就是公平性,或者利用定義條件等。但是,編碼中也需要注意,必須要明確呼叫 unlock() 方法釋放,不然就會一直持有該鎖。

synchronized 和 ReentrantLock 的效能不能一概而論,早期版本 synchronized 在很多場景下效能相差較大,在後續版本進行了較多改進,在低競爭場景中表現可能優於 ReentrantLock。