阿里歷年Java面試題知識點總結
1. Java中的原始資料型別都有哪些,它們的大小及對應的封裝類是什麼?
- boolean
boolean資料型別非true即false。這個資料型別表示1 bit,但是它的大小並沒有精確定義。
《Java虛擬機器規範》中如是說:“雖然定義了boolean這種資料型別,但是隻對它提供了非常有限的支援。在Java虛擬機器中沒有任何供boolean值專用的位元組碼指令,Java語言表示式所操作的boolean值,在編譯之後都使用Java虛擬機器中的int資料型別來代替,而boolean陣列將會被編碼成Java虛擬機器的byte陣列,每個元素boolean元素佔8位”。這樣我們可以得出boolean型別單獨使用是4個位元組,在陣列中又是1個位元組。
綜上,我們可以知道:官方文件對boolean型別沒有給出精確的定義,《Java虛擬機器規範》給出了“單獨時使用4個位元組,boolean陣列時1個位元組”的定義,具體還要看虛擬機器實現是否按照規範來,所以1個位元組、4個位元組都是有可能的。這其實是一種時空權衡。
boolean型別的封裝類是Boolean。 - byte——1 byte——Byte
- short——2 bytes——Short
- int——4 bytes——Integer
- long——8 bytes——Long
- float——4 bytes——Float
- double——8 bytes——Double
- char——2 bytes——Character
2. 談一談”==“與”equals()"的區別。
《Think in Java》中說:“關係操作符生成的是一個boolean結果,它們計算的是運算元的值之間的關係”。
"=="判斷的是兩個物件的記憶體地址是否一樣,適用於原始資料型別和列舉型別(它們的變數儲存的是值本身,而引用型別變數儲存的是引用);equals是Object類的方法,Object對它的實現是比較記憶體地址,我們可以重寫這個方法來自定義“相等”這個概念。比如類庫中的String、Date等類就對這個方法進行了重寫。
綜上,對於列舉型別和原始資料型別的相等性比較,應該使用"==";對於引用型別的相等性比較,應該使用equals方法。
3. Java中的四種引用及其應用場景是什麼?
- 強引用: 通常我們使用new操作符建立一個物件時所返回的引用即為強引用
- 軟引用: 若一個物件只能通過軟引用到達,那麼這個物件在記憶體不足時會被回收,可用於圖片快取中,記憶體不足時系統會自動回收不再使用的Bitmap
- 弱引用: 若一個物件只能通過弱引用到達,那麼它就會被回收(即使記憶體充足),同樣可用於圖片快取中,這時候只要Bitmap不再使用就會被回收
- 虛引用: 虛引用是Java中最“弱”的引用,通過它甚至無法獲取被引用的物件,它存在的唯一作用就是當它指向的物件回收時,它本身會被加入到引用佇列中,這樣我們可以知道它指向的物件何時被銷燬。
4. object中定義了哪些方法?
clone(), equals(), hashCode(), toString(), notify(), notifyAll(), wait(), finalize(), getClass()
5. hashCode的作用是什麼?
請參見散列表的基本原理與實現
6. ArrayList, LinkedList, Vector的區別是什麼?
- ArrayList: 內部採用陣列儲存元素,支援高效隨機訪問,支援動態調整大小
- LinkedList: 內部採用連結串列來儲存元素,支援快速插入/刪除元素,但不支援高效地隨機訪問
- Vector: 可以看作執行緒安全版的ArrayList
7. String, StringBuilder, StringBuffer的區別是什麼?
- String: 不可變的字元序列,若要向其中新增新字元需要建立一個新的String物件
- StringBuilder: 可變字元序列,支援向其中新增新字元(無需建立新物件)
- StringBuffer: 可以看作執行緒安全版的StringBuilder
8. Map, Set, List, Queue、Stack的特點及用法。
- Map<K, V>: Java中儲存鍵值對的資料型別都實現了這個介面,表示“對映表”。支援的兩個核心操作是get(Object key)以及put(K key, V value),分別用來獲取鍵對應的值以及向對映表中插入鍵值對。
- Set<E>: 實現了這個介面的集合型別中不允許存在重複的元素,代表數學意義上的“集合”。它所支援的核心操作有add(E e),* remove(Object o)*, contains(Object o),分別用於新增元素,刪除元素以及判斷給定元素是否存在於集中。
- List<E>: Java中集合框架中的列表型別都實現了這個介面,表示一種有序序列。支援get(int index), add(E e)等操作。
- Queue<E>: Java集合框架中的佇列介面,代表了“先進先出”佇列。支援add(E element), remove()等操作。
- Stack<E>: Java集合框架中表示堆疊的資料型別,堆疊是一種“後進先出”的資料結構。支援push(E item), pop()等操作。
更詳細的說明請參考官方文件,對相關資料結構不太熟悉的同學可以參考《演算法導論》或其他相關書籍。
9. HashMap和HashTable的區別
- HashTable是執行緒安全的,而HashMap不是
- HashMap中允許存在null鍵和null值,而HashTable中不允許
10. HashMap的實現原理
簡單的說,HashMap的底層實現是“基於拉鍊法的散列表”。詳細分析請參考深入解析HashMap、HashTable
11. ConcurrentHashMap的實現原理
ConcurrentHashMap是支援併發讀寫的HashMap,它的特點是讀取資料時無需加鎖,寫資料時可以保證加鎖粒度儘可能的小。由於其內部採用“分段儲存”,只需對要進行寫操作的資料所在的“段”進行加鎖。關於ConcurrentHashMap底層實現的詳細分析請參考Java併發程式設計:併發容器之ConcurrentHashMap
12. TreeMap, LinkedHashMap, HashMap的區別是什麼?
- HashMap的底層實現是散列表,因此它內部儲存的元素是無序的;
- TreeMap的底層實現是紅黑樹,所以它內部的元素的有序的。排序的依據是自然序或者是建立TreeMap時所提供的比較器(Comparator)物件。
- LinkedHashMap可以看作能夠記住插入元素的順序的HashMap。
13. Collection與Collections的區別是什麼?
- Collection<E>是Java集合框架中的基本介面;
- Collections是Java集合框架提供的一個工具類,其中包含了大量用於操作或返回集合的靜態方法。
14. 對於“try-catch-finally”,若try語句塊中包含“return”語句,finally語句塊會執行嗎?
會執行。只有兩種情況finally塊中的語句不會被執行:
- 呼叫了System.exit()方法;
- JVM“崩潰”了。
15. Java中的異常層次結構
Java中的異常層次結構如下圖所示:
我們可以看到Throwable類是異常層級中的基類。Error類表示內部錯誤,這類錯誤使我們無法控制的;Exception表示異常,RuntimeException及其子類屬於未檢查異常,這類異常包括ArrayIndexOutOfBoundsException、NullPointerException等,我們應該通過條件判斷等方式語句避免未檢查異常的發生。IOException及其子類屬於已檢查異常,編譯器會檢查我們是否為所有可能丟擲的已檢查異常提供了異常處理器,若沒有則會報錯。對於未檢查異常,我們無需捕獲(當然Java也允許我們捕獲,但我們應該做的事避免未檢查異常的發生)。
16. Java面向物件的三個特徵與含義
三大特徵:封裝、繼承、多型。詳細介紹請戳Java面向物件三大特性
17. Override, Overload的含義與區別
- Override表示“重寫”,是子類對父類中同一方法的重新定義
- Overload表示“過載”,也就是定義一個與已定義方法名稱相同但簽名不同的新方法
18. 介面與抽象類的區別
- 介面是一種約定,實現介面的類要遵循這個約定;
- 抽象類本質上是一個類,使用抽象類的代價要比介面大。
- 介面與抽象類的對比如下:
- 抽象類中可以包含屬性,方法(包含抽象方法與有著具體實現的方法),常量;介面只能包含常量和方法宣告。
- 抽象類中的方法和成員變數可以定義可見性(比如public、private等);而介面中的方法只能為public(預設為public)。
- 一個子類只能有一個父類(具體類或抽象類);而一個介面可以繼承一個多個介面,一個類也可以實現多個介面。
- 子類中實現父類中的抽象方法時,可見性可以大於等於父類中的;而介面實現類中的介面 方法的可見性只能與介面中相同(public)。
19. 靜態內部類與非靜態內部類的區別
靜態內部類不會持有外圍類的引用,而非靜態內部類會隱式持有外圍類的一個引用。
20. Java中多型的實現原理
所謂多型,指的就是父類引用指向子類物件,呼叫方法時會呼叫子類的實現而不是父類的實現。多型的實現的關鍵在於“動態繫結”。詳細介紹請戳Java動態繫結的內部實現機制
21. 簡述Java中建立新執行緒的兩種方法
- 繼承Thread類(假設子類為MyThread),並重寫run()方法,然後new一個MyThread物件並對其呼叫start()即可啟動新執行緒。
- 實現Runnable介面(假設實現類為MyRunnable),而後將MyRunnable物件作為引數傳入Thread構造器,在得到的Thread物件上呼叫start()方法即可。
22. 簡述Java中進行執行緒同步的方法
- volatile: Java Memory Model保證了對同一個volatile變數的寫happens before對它的讀;
- synchronized: 可以來對一個程式碼塊或是對一個方法上鎖,被“鎖住”的地方稱為臨界區,進入臨界區的執行緒會獲取物件的monitor,這樣其他嘗試進入臨界區的執行緒會因無法獲取monitor而被阻塞。由於等待另一個執行緒釋放monitor而被阻塞的執行緒無法被中斷。
- ReentrantLock: 嘗試獲取鎖的執行緒可以被中斷並可以設定超時引數。
23. 簡述Java中具有哪幾種粒度的鎖
Java中可以對類、物件、方法或是程式碼塊上鎖。
24. 給出“生產者-消費者”問題的一種解決方案
使用阻塞佇列:
public class BlockingQueueTest {
private int size = 20;
private ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(size);
public static void main(String[] args) {
BlockingQueueTest test = new BlockingQueueTest();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start(); consumer.start();
}
class Consumer extends Thread{
@Override
public void run() {
while(true){
try {
//從阻塞佇列中取出一個元素
queue.take();
System.out.println("佇列剩餘" + queue.size() + "個元素");
} catch (InterruptedException e) {
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
while (true) {
try {
//向阻塞佇列中插入一個元素
queue.put(1);
System.out.println("佇列剩餘空間:" + (size - queue.size()));
} catch (InterruptedException e) {
}
}
}
}
}
25. ThreadLocal的設計理念與作用
ThreadLocal的作用是提供執行緒內的區域性變數,在多執行緒環境下訪問時能保證各個執行緒內的ThreadLocal變數各自獨立。也就是說,每個執行緒的ThreadLocal變數是自己專用的,其他執行緒是訪問不到的。ThreadLocal最常用於以下這個場景:多執行緒環境下存在對非執行緒安全物件的併發訪問,而且該物件不需要線上程間共享,但是我們不想加鎖,這時候可以使用ThreadLocal來使得每個執行緒都持有一個該物件的副本。
26. concurrent包的整體架構
27. ArrayBlockingQueue, CountDownLatch類的作用
- CountDownLatch: 允許執行緒集等待直到計數器為0。適用場景: 當一個或多個執行緒需要等待指定數目的事件發生後再繼續執行。
- ArrayBlockingQueue: 一個基於陣列實現的阻塞佇列,它在構造時需要指定容量。當試圖向滿佇列中新增元素或者從空佇列中移除元素時,當前執行緒會被阻塞。通過阻塞佇列,我們可以按以下模式來工作:工作者執行緒可以週期性的將中間結果放入阻塞佇列中,其它執行緒可以取出中間結果並進行進一步操作。若工作者執行緒的執行比較慢(還沒來得及向佇列中插入元素),其他從佇列中取元素的執行緒會等待它(試圖從空佇列中取元素從而阻塞);若工作者執行緒執行較快(試圖向滿佇列中插入元素),則它會等待其它執行緒取出元素再繼續執行。
28. wait(),sleep() 的區別
- wait(): Object類中定義的例項方法。在指定物件上呼叫wait方法會讓當前執行緒進入等待狀態(前提是當前執行緒持有該物件的monitor),此時當前執行緒會釋放相應物件的monitor,這樣一來其它執行緒便有機會獲取這個物件的monitor了。當其它執行緒獲取了這個物件的monitor並進行了所需操作時,便可以呼叫notify方法喚醒之前進入等待狀態的執行緒。
- sleep(): Thread類中的靜態方法,作用是讓當前執行緒進入休眠狀態,以便讓其他執行緒有機會執行。進入休眠狀態的執行緒不會釋放它所持有的鎖。
29. 執行緒池的用法與優勢
- 優勢: 實現對執行緒的複用,避免了反覆建立及銷燬執行緒的開銷;使用執行緒池統一管理執行緒可以減少併發執行緒的數目,而執行緒數過多往往會線上程上下文切換上以及執行緒同步上浪費過多時間。
- 用法: 我們可以呼叫ThreadPoolExecutor的某個構造方法來自己建立一個執行緒池。但通常情況下我們可以使用Executors類提供給我們的靜態工廠方法來更方便的建立一個執行緒池物件。建立了執行緒池物件後,我們就可以呼叫submit方法提交任務到執行緒池中去執行了;執行緒池使用完畢後我們要記得呼叫shutdown方法來關閉它。
30. for-each與常規for迴圈的效率對比
關於這個問題我們直接看《Effective Java》給我們做的解答:
for-each能夠讓程式碼更加清晰,並且減少了出錯的機會。
下面的慣用程式碼適用於集合與陣列型別:
for (Element e : elements) {
doSomething(e);
}
使用for-each迴圈與常規的for迴圈相比,並不存在效能損失,即使對陣列進行迭代也是如此。實際上,在有些場合下它還能帶來微小的效能提升,因為它只計算一次陣列索引的上限。
31. 簡述Java IO與NIO的區別
- Java IO是面向流的,這意味著我們需要每次從流中讀取一個或多個位元組,直到讀取完所有位元組;NIO是面向緩衝的,也就是說會把資料讀取到一個緩衝區中,然後對緩衝區中的資料進行相應處理。
- Java IO是阻塞IO,而NIO是非阻塞IO。
- Java NIO中存在一個稱為選擇器(selector)的東西,它允許你把多個通道(channel)註冊到一個選擇器上,然後使用一個執行緒來監視這些通道:若這些通道里有某個準備好可以開始進行讀或寫操作了,則開始對相應的通道進行讀寫。而在等待某通道變為可讀/寫期間,請求對通道進行讀寫操作的執行緒可以去幹別的事情。
32. 反射的作用與原理
反射的作用概括地說是執行時獲取類的各種定義資訊,比如定義了哪些屬性與方法。原理是通過類的class物件來獲取它的各種資訊。
33. Java中的泛型機制
關於泛型機制的詳細介紹請直接戳Java核心技術點之泛型
34. Java 7與Java 8的新特性
這裡有兩篇總結的非常好的:
35. 常見設計模式
所謂“設計模式”,不過是面向物件程式設計中一些常用的軟體設計手法,並且經過實踐的檢驗,這些設計手法在各自的場景下能解決一些需求,因此它們就成為了如今廣為流傳的”設計模式“。也就是說,正式因為在某些場景下產生了一些棘手的問題,才催生了相應的設計模式。明確了這一點,我們在學習某種設計模式時要充分理解它產生的背景以及它所解決的主要矛盾是什麼。
常用的設計模式可以分為以下三大類:
- 建立型模式: 包括工廠模式(又可進一步分為簡單工廠模式、工廠方法模式、抽象工廠模式)、建造者模式、單例模式。
- 結構型模式: 包括介面卡模式、橋接模式、裝飾模式、外觀模式、享元模式、代理模式。
- 行為型模式: 包括命令模式、中介者模式、觀察者模式、狀態模式、策略模式。
關於每個模式具體的介紹請參考圖說設計模式
36. JNI的基本用法
關於JNI,這裡有篇好文:Android中的JNI
37. 動態代理的定義、應用場景及原理
38. 註解的基本概念與使用
註解可以看作是“增強版的註釋”,它可以向編譯器、虛擬機器說明一些事情。
註解是描述Java程式碼的程式碼,它能夠被編譯器解析,註解處理工具在執行時也能夠解析註解。註解本身是“被動”的資訊,只有主動解析它才有意義。
除了向編譯器/虛擬機器傳遞資訊,我們也可以使用註解來生成一些“模板化”的程式碼。
關於註解更進一步的分析請參考Java核心技術點之註解
我有一個微信公眾號,經常會分享一些Java技術相關的乾貨;如果你喜歡我的分享,可以用微信搜尋“Java團長”或者“javatuanzhang”關注。