這是我見過最有用的java面試題,面試了無數公司總結的(含答案)

編程語言 Java 面試 技術 java高級 2018-12-07

(一小部分題的答案被我略作改動)

1、什麼是線程局部變量?

線程局部變量是侷限於線程內部的變量,屬於線程自身所有,不在多個線程間共享。Java 提供 ThreadLocal 類來支持線程局部變量,是一種實現線程安全的方式。但是在管理環境下(如 web 服務器)使用線程局部變量的時候要特別小心,在這種情況下,工作線程的生命週期比任何應用變量的生命週期都要長。任何線程局部變量一旦在工作完成後沒有釋放,Java 應用就存在內存洩露的風險。

2.用 wait-notify 寫一段代碼來解決生產者-消費者問題?

請參考答案中的示例代碼。只要記住在同步塊中調用 wait() 和 notify()方法,如果阻塞,通過循環來測試等待條件。

【生產者】


package com.edu.chapter03.test;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Producer implements Runnable {

private final Vector sharedQueue;
private final int SIZE;

public Producer(Vector sharedQueue, int size) {
this.sharedQueue = sharedQueue;
this.SIZE = size;
}

@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 7; i++) {
System.out.println("Produced:" + i);
try {
produce(i);
} catch (InterruptedException ex) {
Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}

private void produce(int i) throws InterruptedException {

//wait if queue is full
while (sharedQueue.size() == SIZE) {
synchronized (sharedQueue) {
System.out.println("Queue is full " + Thread.currentThread().getName()
+ " is waiting , size: " + sharedQueue.size());
sharedQueue.wait();
}
}

//producing element and notify consumers
synchronized (sharedQueue) {
sharedQueue.add(i);
sharedQueue.notifyAll();
}
}
}

【消費者】


package com.edu.chapter03.test;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Consumer implements Runnable {

private final Vector sharedQueue;
private final int SIZE;

public Consumer(Vector sharedQueue, int size) {
this.sharedQueue = sharedQueue;
this.SIZE = size;
}

@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
try {
System.out.println("Consumer: " + consume());
Thread.sleep(50);
} catch (InterruptedException ex) {
Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}

private int consume() throws InterruptedException {

//wait if queue is empty
while (sharedQueue.isEmpty()) {
synchronized (sharedQueue) {
System.out.println("Queue is empty " + Thread.currentThread().getName()
+ " is waiting , size: " + sharedQueue.size());
sharedQueue.wait();
}
}

//otherwise consume element and notify waiting producer
synchronized (sharedQueue) {
sharedQueue.notifyAll();
return (Integer) sharedQueue.remove(0);
}
}
}

【測試函數】


package com.edu.chapter03.test;
import java.util.Vector;

public class ProducerConsumerSolution {

public static void main(String[] args) {
Vector sharedQueue = new Vector();
int size = 4;
Thread prodThread = new Thread(new Producer(sharedQueue, size), "Producer");
Thread consThread = new Thread(new Consumer(sharedQueue, size), "Consumer");
prodThread.start();
consThread.start();
}
}

3. 用 Java 寫一個線程安全的單例模式(Singleton)?

請參考答案中的示例代碼,這裡面一步一步教你創建一個線程安全的 Java 單例類。當我們說線程安全時,意思是即使初始化是在多線程環境中,仍然能保證單個實例。Java 中,使用枚舉作為單例類是最簡單的方式來創建線程安全單例模式的方式。

立即加載/餓漢式:

【在調用方法前,實例就已經被創建】


package com.weishiyao.learn.day8.singleton.ep1;

public class MyObject {
// 立即加載方式==惡漢模式
private static MyObject myObject = new MyObject();

private MyObject() {
}

public static MyObject getInstance() {
// 此代碼版本為立即加載
// 此版本代碼的缺點是不能有其他實例變量
// 因為getInstance()方法沒有同步
// 所以有可能出現非線程安全的問題
return myObject;
}
}

【創建線程類】


package com.weishiyao.learn.day8.singleton.ep1;

public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}

【創建運行類】


package com.weishiyao.learn.day8.singleton.ep1;

public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
}
}

延遲加載/懶漢式

【建對象的實例】


package com.weishiyao.learn.day8.singleton.ep2;

public class MyObject {
private static MyObject myObject;

private MyObject() {

}

public static MyObject getInstance() {
// 延遲加載
if (myObject != null) {

} else {
myObject = new MyObject();
}
return myObject;
}
}

【創建線程類】


package com.weishiyao.learn.day8.singleton.ep2;

public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance().hashCode());
}
}

【創建運行類】


package com.weishiyao.learn.day8.singleton.ep2;

public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
}
}

【運行測試類】


package com.weishiyao.learn.day8.singleton.ep2;

public class Run {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
MyThread t4 = new MyThread();
MyThread t5 = new MyThread();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}

4.Java 中 sleep 方法和 wait 方法的區別?

雖然兩者都是用來暫停當前運行的線程,但是 sleep() 實際上只是短暫停頓,因為它不會釋放鎖,而 wait() 意味著條件等待,這就是為什麼該方法要釋放鎖,因為只有這樣,其他等待的線程才能在滿足條件時獲取到該鎖。

5.什麼是不可變對象(immutable object)?Java 中怎麼創建一個不可變對象?

不可變對象指對象一旦被創建,狀態就不能再改變。任何修改都會創建一個新的對象,如 String、Integer及其它包裝類。詳情參見答案,一步一步指導你在 Java 中創建一個不可變的類。

6.我們能創建一個包含可變對象的不可變對象嗎?

是的,我們是可以創建一個包含可變對象的不可變對象的,你只需要謹慎一點,不要共享可變對象的引用就可以了,如果需要變化時,就返回原對象的一個拷貝。最常見的例子就是對象中包含一個日期對象的引用。

7.Java 中應該使用什麼數據類型來代表價格?

如果不是特別關心內存和性能的話,使用BigDecimal,否則使用預定義精度的 double 類型。

8.怎麼將 byte 轉換為 String?

可以使用 String 接收 byte[] 參數的構造器來進行轉換,需要注意的點是要使用的正確的編碼,否則會使用平臺默認編碼,這個編碼可能跟原來的編碼相同,也可能不同。

9.Java 中 bytes 與其他類型的轉換?


public class Test {
private static ByteBuffer buffer = ByteBuffer.allocate(8);
public static void main(String[] args) {

//測試 int 轉 byte
int int0 = 234;
byte byte0 = intToByte(int0);
System.out.println("byte0=" + byte0);//byte0=-22
//測試 byte 轉 int
int int1 = byteToInt(byte0);
System.out.println("int1=" + int1);//int1=234

//測試 int 轉 byte 數組
int int2 = 1417;
byte[] bytesInt = intToByteArray(int2);
System.out.println("bytesInt=" + bytesInt);//bytesInt=[B@de6ced
//測試 byte 數組轉 int
int int3 = byteArrayToInt(bytesInt);
System.out.println("int3=" + int3);//int3=1417


//測試 long 轉 byte 數組
long long1 = 2223;
byte[] bytesLong = longToBytes(long1);
System.out.println("bytes=" + bytesLong);//bytes=[B@c17164
//測試 byte 數組 轉 long
long long2 = bytesToLong(bytesLong);
System.out.println("long2=" + long2);//long2=2223
}


//byte 與 int 的相互轉換
public static byte intToByte(int x) {
return (byte) x;
}

public static int byteToInt(byte b) {
//Java 總是把 byte 當做有符處理;我們可以通過將其和 0xFF 進行二進制與得到它的無符值
return b & 0xFF;
}

//byte 數組與 int 的相互轉換
public static int byteArrayToInt(byte[] b) {
return b[3] & 0xFF |
(b[2] & 0xFF) << 8 |
(b[1] & 0xFF) << 16 |
(b[0] & 0xFF) << 24;
}

public static byte[] intToByteArray(int a) {
return new byte[] {
(byte) ((a >> 24) & 0xFF),
(byte) ((a >> 16) & 0xFF),
(byte) ((a >> 8) & 0xFF),
(byte) (a & 0xFF)
};
}
//byte 數組與 long 的相互轉換
public static byte[] longToBytes(long x) {
buffer.putLong(0, x);
return buffer.array();
}
public static long bytesToLong(byte[] bytes) {
buffer.put(bytes, 0, bytes.length);
buffer.flip();//need flip
return buffer.getLong();
}
}

關注我:私信回覆“架構資料”獲取往期Java高級架構資料、源碼、筆記、視頻

Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式、高併發等架構技術

10.我們能將 int 強制轉換為 byte 類型的變量嗎?如果該值大於 byte 類型的範圍,將會出現什麼現象?

是的,我們可以做強制轉換,但是 Java 中 int 是 32 位的,而 byte 是 8 位的,所以,如果強制轉化是,int 類型的高 24 位將會被丟棄,byte 類型的範圍是從 -128 到 128。

11.存在兩個類,B 繼承 A,C 繼承 B,我們能將 B 轉換為 C 麼?如 C = (C) B;

可以,向下轉型。但是不建議使用,容易出現類型轉型異常。

12.哪個類包含 clone 方法?是 Cloneable 還是 Object?

java.lang.Cloneable 是一個標示性接口,不包含任何方法,clone 方法在 object 類中定義。並且需要知道 clone() 方法是一個本地方法,這意味著它是由 c 或 c++ 或 其他本地語言實現的。

13.Java 中 ++ 操作符是線程安全的嗎?

不是線程安全的操作。它涉及到多個指令,如讀取變量值,增加,然後存儲回內存,這個過程可能會出現多個線程交差。

14.a = a + b 與 a += b 的區別

+= 隱式的將加操作的結果類型強制轉換為持有結果的類型。如果兩這個整型相加,如 byte、short 或者 int,首先會將它們提升到 int 類型,然後在執行加法操作。如果加法操作的結果比 a 的最大值要大,則 a+b 會出現編譯錯誤,但是 a += b 沒問題,如下:

byte a = 127;

byte b = 127;

b = a + b; // error : cannot convert from int to byte

b += a; // ok

(譯者注:這個地方應該表述的有誤,其實無論 a+b 的值為多少,編譯器都會報錯,因為 a+b 操作會將 a、b 提升為 int 類型,所以將 int 類型賦值給 byte 就會編譯出錯)

15.我能在不進行強制轉換的情況下將一個 double 值賦值給 long 類型的變量嗎?

不行,你不能在沒有強制類型轉換的前提下將一個 double 值賦值給 long 類型的變量,因為 double 類型的範圍比 long 類型更廣,所以必須要進行強制轉換。

16. 3*0.1 == 0.3 將會返回什麼?true 還是 false?

false,因為有些浮點數不能完全精確的表示出來。

17.int 和 Integer 哪個會佔用更多的內存?

Integer 對象會佔用更多的內存。Integer 是一個對象,需要存儲對象的元數據。但是 int 是一個原始類型的數據,所以佔用的空間更少。

18.為什麼 Java 中的 String 是不可變的(Immutable)?

Java 中的 String 不可變是因為 Java 的設計者認為字符串使用非常頻繁,將字符串設置為不可變可以允許多個客戶端之間共享相同的字符串。更詳細的內容參見答案。

19.我們能在 Switch 中使用 String 嗎?

從 Java 7 開始,我們可以在 switch case 中使用字符串,但這僅僅是一個語法糖。內部實現在 switch 中使用字符串的 hash code。

20.Java 中的構造器鏈是什麼?

當你從一個構造器中調用另一個構造器,就是Java 中的構造器鏈。這種情況只在重載了類的構造器的時候才會出現。

21.64 位 JVM 中,int 的長度是多數?

Java 中,int 類型變量的長度是一個固定值,與平臺無關,都是 32 位。意思就是說,在 32 位 和 64 位 的Java 虛擬機中,int 類型的長度是相同的。

22.Serial 與 Parallel GC之間的不同之處?

Serial 與 Parallel 在GC執行的時候都會引起 stop-the-world。它們之間主要不同 serial 收集器是默認的複製收集器,執行 GC 的時候只有一個線程,而 parallel 收集器使用多個 GC 線程來執行。

23. 32 位和 64 位的 JVM,int 類型變量的長度是多數?

32 位和 64 位的 JVM 中,int 類型變量的長度是相同的,都是 32 位或者 4 個字節。

24.Java 中 WeakReference 與 SoftReference的區別?

雖然 WeakReference 與 SoftReference 都有利於提高 GC 和 內存的效率,但是 WeakReference ,一旦失去最後一個強引用,就會被 GC 回收,而軟引用雖然不能阻止被回收,但是可以延遲到 JVM 內存不足的時候。

25.WeakHashMap 是怎麼工作的?

WeakHashMap 的工作與正常的 HashMap 類似,但是使用弱引用作為 key,意思就是當 key 對象沒有任何引用時,key/value 將會被回收。

26.JVM 選項 -XX:+UseCompressedOops 有什麼作用?為什麼要使用?

當你將你的應用從 32 位的 JVM 遷移到 64 位的 JVM 時,由於對象的指針從 32 位增加到了 64 位,因此堆內存會突然增加,差不多要翻倍。這也會對 CPU 緩存(容量比內存小很多)的數據產生不利的影響。因為,遷移到 64 位的 JVM 主要動機在於可以指定最大堆大小,通過壓縮 OOP 可以節省一定的內存。通過 -XX:+UseCompressedOops 選項,JVM 會使用 32 位的 OOP,而不是 64 位的 OOP。

27.怎樣通過 Java 程序來判斷 JVM 是 32 位 還是 64 位?

你可以檢查某些系統屬性如 sun.arch.data.model 或 os.arch 來獲取該信息。

28.32 位 JVM 和 64 位 JVM 的最大堆內存分別是多數?

理論上說上 32 位的 JVM 堆內存可以到達 2^32,即 4GB,但實際上會比這個小很多。不同操作系統之間不同,如 Windows 系統大約 1.5 GB,Solaris 大約 3GB。64 位 JVM允許指定最大的堆內存,理論上可以達到 2^64,這是一個非常大的數字,實際上你可以指定堆內存大小到 100GB。甚至有的 JVM,如 Azul,堆內存到 1000G 都是可能的。

29.JRE、JDK、JVM 及 JIT 之間有什麼不同?

JRE 代表 Java 運行時(Java run-time),是運行 Java 引用所必須的。JDK 代表 Java 開發工具(Java development kit),是 Java 程序的開發工具,如 Java 編譯器,它也包含 JRE。JVM 代表 Java 虛擬機(Java virtual machine),它的責任是運行 Java 應用。JIT 代表即時編譯(Just In Time compilation),當代碼執行的次數超過一定的閾值時,會將 Java 字節碼轉換為本地代碼,如,主要的熱點代碼會被準換為本地代碼,這樣有利大幅度提高 Java 應用的性能。

30.解釋 Java 堆空間及 GC?

當通過 Java 命令啟動 Java 進程的時候,會為它分配內存。內存的一部分用於創建堆空間,當程序中創建對象的時候,就從對空間中分配內存。GC 是 JVM 內部的一個進程,回收無效對象的內存用於將來的分配。

31.你能保證 GC 執行嗎?

不能,雖然你可以調用 System.gc() 或者 Runtime.gc(),但是沒有辦法保證 GC 的執行。

32.怎麼獲取 Java 程序使用的內存?堆使用的百分比?

可以通過 java.lang.Runtime 類中與內存相關方法來獲取剩餘的內存,總內存及最大堆內存。通過這些方法你也可以獲取到堆使用的百分比及堆內存的剩餘空間。

Runtime.freeMemory() 方法返回剩餘空間的字節數,Runtime.totalMemory() 方法總內存的字節數,Runtime.maxMemory() 返回最大內存的字節數。

33.Java 中堆和棧有什麼區別?(答案)

JVM 中堆和棧屬於不同的內存區域,使用目的也不同。棧常用於保存方法幀和局部變量,而對象總是在堆上分配。棧通常都比堆小,也不會在多個線程之間共享,而堆被整個 JVM 的所有線程共享。

34. “a==b”和”a.equals(b)”有什麼區別?

如果 a 和 b 都是對象,則 a==b 是比較兩個對象的引用,只有當 a 和 b 指向的是堆中的同一個對象才會返回 true,而 a.equals(b) 是進行邏輯比較,所以通常需要重寫該方法來提供邏輯一致性的比較。例如,String 類重寫 equals() 方法,所以可以用於兩個不同對象,但是包含的字母相同的比較。

35.a.hashCode() 有什麼用?與 a.equals(b) 有什麼關係?

hashCode() 方法是相應對象整型的 hash 值。它常用於基於 hash 的集合類,如 Hashtable、HashMap、LinkedHashMap等等。它與 equals() 方法關係特別緊密。根據 Java 規範,兩個使用 equal() 方法來判斷相等的對象,必須具有相同的 hash code。

36.final、finalize 和 finally 的不同之處?

final 是一個修飾符,可以修飾變量、方法和類。如果 final 修飾變量,意味著該變量的值在初始化後不能被改變。finalize 方法是在對象被回收之前調用的方法,給對象自己最後一個復活的機會,但是什麼時候調用 finalize 沒有保證。finally 是一個關鍵字,與 try 和 catch 一起用於異常的處理。finally 塊一定會被執行,無論在 try 塊中是否有發生異常。

37.Java 中的編譯期常量是什麼?使用它又什麼風險?

公共靜態不可變(public static final )變量也就是我們所說的編譯期常量,這裡的 public 可選的。實際上這些變量在編譯時會被替換掉,因為編譯器知道這些變量的值,並且知道這些變量在運行時不能改變。這種方式存在的一個問題是你使用了一個內部的或第三方庫中的公有編譯時常量,但是這個值後面被其他人改變了,但是你的客戶端仍然在使用老的值,甚至你已經部署了一個新的jar。為了避免這種情況,當你在更新依賴 JAR 文件時,確保重新編譯你的程序。

38.List、Set、Map 和 Queue 之間的區別

List 是一個有序集合,允許元素重複。它的某些實現可以提供基於下標值的常量訪問時間,但是這不是 List 接口保證的。Set 是一個無序集合。

39.poll() 方法和 remove() 方法的區別?

poll() 和 remove() 都是從隊列中取出一個元素,但是 poll() 在獲取元素失敗的時候會返回空,但是 remove() 失敗的時候會拋出異常。

40.Java 中 LinkedHashMap 和 PriorityQueue 的區別是什麼?

PriorityQueue 保證最高或者最低優先級的的元素總是在隊列頭部,但是 LinkedHashMap 維持的順序是元素插入的順序。當遍歷一個 PriorityQueue 時,沒有任何順序保證,但是 LinkedHashMap 課保證遍歷順序是元素插入的順序。

41.ArrayList 與 LinkedList 的不區別?

最明顯的區別是 ArrrayList 底層的數據結構是數組,支持隨機訪問,而 LinkedList 的底層數據結構書鏈表,不支持隨機訪問。使用下標訪問一個元素,ArrayList 的時間複雜度是 O(1),而 LinkedList 是 O(n)。更多細節的討論參見答案。

42.用哪兩種方式來實現集合的排序?

你可以使用有序集合,如 TreeSet 或 TreeMap,你也可以使用有順序的的集合,如 list,然後通過 Collections.sort() 來排序。

43.Java 中怎麼打印數組?(answe

你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法來打印數組。由於數組沒有實現 toString() 方法,所以如果將數組傳遞給 System.out.println() 方法,將無法打印出數組的內容,但是 Arrays.toString() 可以打印每個元素。

44.Java 中的 LinkedList 是單向鏈表還是雙向鏈表?

是雙向鏈表,你可以檢查 JDK 的源碼。在 Eclipse,你可以使用快捷鍵 Ctrl + T,直接在編輯器中打開該類。

45.Java 中的 TreeMap 是採用什麼樹實現的?

Java 中的 TreeMap 是使用紅黑樹實現的。

46. Hashtable 與 HashMap 有什麼不同之處?

這兩個類有許多不同的地方,下面列出了一部分:

a) Hashtable 是 JDK 1 遺留下來的類,而 HashMap 是後來增加的。

b)Hashtable 是同步的,比較慢,但 HashMap 沒有同步策略,所以會更快。

c)Hashtable 不允許有個空的 key,但是 HashMap 允許出現一個 null key。

更多的不同之處參見答案。

47.Java 中的 HashSet,內部是如何工作的?

HashSet 的內部採用 HashMap來實現。由於 Map 需要 key 和 value,所以所有 key 的都有一個默認 value。類似於 HashMap,HashSet 不允許重複的 key,只允許有一個null key,意思就是 HashSet 中只允許存儲一個 null 對象。

48.寫一段代碼在遍歷 ArrayList 時移除一個元素?

該問題的關鍵在於面試者使用的是 ArrayList 的 remove() 還是 Iterator 的 remove()方法。這有一段示例代碼,是使用正確的方式來實現在遍歷的過程中移除元素,而不會出現 ConcurrentModificationException 異常的示例代碼。

49.我們能自己寫一個容器類,然後使用 for-each 循環碼?

可以,你可以寫一個自己的容器類。如果你想使用 Java 中增強的循環來遍歷,你只需要實現 Iterable 接口。如果你實現 Collection 接口,默認就具有該屬性。

50.ArrayList 和 HashMap 的默認大小是多數?

在 Java 7 中,ArrayList 的默認大小是 10 個元素,HashMap 的默認大小是16個元素(必須是2的冪)。這就是 Java 7 中 ArrayList 和 HashMap 類的代碼片段:


// from ArrayList.java JDK 1.7
private static final int DEFAULT_CAPACITY = 10;
//from HashMap.java JDK 7
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

關注我:私信回覆“架構資料”獲取往期Java高級架構資料、源碼、筆記、視頻

Dubbo、Redis、Netty、zookeeper、Spring cloud、分佈式、高併發等架構技術

相關推薦

推薦中...