歡迎光臨
每天分享高質量文章

Java面試題及其解答

來自:苦逼的碼農(微信號:di201805)

作者:帥地

個人簡介:一個熱愛編程的在校生,我的世界不只有coding,還有writing。目前維護訂閱號「苦逼的碼農」,專註於寫「演算法與資料結構」,「Java」,「計算機網絡」。

Java基礎

1、java中 == 和equals() 和 hashCode() 的區別

(1)、== 是運算子,a == b 比較的是 a,b的數值是否相等。

(2)、equals 是 Object 類的一個方法,預設情況下比較兩個物件是否是同一個物件,內部的實現是通過 == 來比較兩個物件的記憶體地址是否相等,其原始碼如下

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

如果想比較兩個物件的其他內容,則可以通過重寫 equals方法,例如 String 類就重寫了 equals 方法,改成了物件的內容是否相等。

(3)、hashCode 也是Object 類的一個方法,傳回值是該物件的哈希碼,同一個物件的哈希碼一定相等,但不同物件的哈希碼也是有可能相等的。

(4)、hashCode() 與 equals() 的關係:如果兩個物件根據 equals() 方法比較相等,那麼這兩個物件的 hashCode() 傳回值一定相等;如果兩個物件根據 equals() 方法比較不相等,那麼這兩個物件的 hashCode() 傳回值不一定不相等

2、int與integer的區別

int 是一個基本數值型別,Integer 是一個物件,Integer 是 int 的一個包裝型別,兩者可以通過拆箱和裝箱自動轉換。

拓展一下:

1Integer a, b;

對於 a == b,比較的是 a 與 b 是否是同一個物件,但是如果運算子 == 左右兩邊算術運算的話,則 a,b會自動拆箱成 int 型別來進行比較。

JVM

1、什麼情況下會觸發類的初始化?

(1)、遇到 new, getstatic, putstatic, invokestatic 這4條位元組碼指令。

(2)、使用 java.lang.reflect 包的方法對類進行反射呼叫。

(3)、初始化一個類的時候,如果發現其父類還沒有進行過初始化,則先初始化其父類(註意:
如果是接口的話,則不要求初始化父類)。

(4)、當虛擬機啟動時,用戶需要指定一個要執行的主類(包含 main()方法的那個類),虛擬機會先初始化這個主類。

(5)、當使用JDK 1.7 的動態語言支持時,如果一個 java.lang.invoke.MethodHandle 實體最後的解析結果 REF_getstatic, REF_putstatic, REF_invokeStatic的方法句柄,並且這個方法句柄所對應的類沒有進行過初始化,則先觸發其初始化。

外加幾種不會初始化的例子:

(1)、同類子類取用父類的靜態欄位,不會導致子類初始化。至於是否會觸發子類的加載和驗證,則取決於虛擬機的具體實現。

(2)、通過陣列定義來取用類,也不會觸發類的初始化。例如下麵這個陳述句:

1Animal[] a = new Animao[10];

並不會觸發 Animal 類的初始化。

(3)、取用一個類的常量也不會觸發初始化。

2、談談你對解析與分派的認識。

1.方法在程式真正運行之前就有一個可確定的呼叫版本,並且這個方法的呼叫版本在運行期間是不可變的,即“編譯時可知,運行不可以變”,這類標的的方法的呼叫稱之為解析。

Java 語言中符合“編譯器可知,運行期不可變”這個要求的方法,主要包括靜態方法和私有方法兩大類。

2.解析呼叫一定是個靜態的過程,在編譯期就完全確定,在類加載的解析階段就將涉及的符號取用全部轉變為可以確定的直接取用,不會延遲到運行期再去完成。而分派(Dispatch)呼叫則可能是靜態的也可能是動態的。於是分派方式就有靜態分派和動態分派。

下麵我來解釋下靜態分派和動態分派。

靜態分派

看下麵這段程式

 1//定義幾個類
2
3public abstract class Animal {
4    }
5class Dog extends Animal{
6    }
7class Lion extends Animal{
8    }
9
10class Test4{    
11    public void run(Animal animal){
12        System.out.println("動物跑啊跑");
13    }
14    public void run(Dog dog){
15        System.out.println("小狗跑啊跑");
16    }
17    public void run(Lion lion){
18        System.out.println("獅子跑啊跑");
19    }    
20    //測試
21    public static void main(String[] args){
22        Animal dog = new Dog();
23        Animal lion = new Lion();;
24        Test4 test4 = new Test4();
25        test4.run(dog);
26        test4.run(lion);
27    }
28}

運行結果是

動物跑啊跑

動物跑啊跑

相信大家學過多載的都能猜到是這個結果。但是,為什麼會選擇這個方法進行多載呢?虛擬機是如何選擇的呢?

在此之前我們先來瞭解兩個概念。

先來看一行代碼:

Animal dog = new Dog();

對於這一行代碼,我們把Animal稱之為變數dog的靜態型別,而後面的Dog稱為變數dog的實際型別。

現在我們再來看看虛擬機是根據什麼來多載選擇哪個方法的。

對於靜態型別相同,但實際型別不同的變數,虛擬機在多載的時候是根據引數的靜態型別而不是實際型別作為判斷選擇的。並且靜態型別在編譯器就是已知的了,這也代表在編譯階段,就已經決定好了選擇哪一個多載方法。

由於dog和lion的靜態型別都是Animal,所以選擇了run(Animal animal)這個方法。

靜態分派的典型應用就是方法的多載的,現在應該知道什麼是靜態分派了吧?

動態分派

和靜態分派類似,所謂動態分派就是就是根據方法的實際型別來選擇呼叫哪個方法,而實際型別是需要到達運行期才能知道。像重寫就是動態分派的典型應用了。

更多的詳情可以看我之前寫的一篇文章從jvm角度看懂類初始化、方法多載、重寫。

3、如何⾃定義⼀個類加載器?你使⽤過哪些或者你在什麼場景下需要⼀個⾃定義的類加載器嗎?

可以把自己自定義的類加載器繼承 ClassLoader,然後重寫 findClass() 方法,把自己的類加載邏輯寫到 findClass() 方法中去。

使用類加載器的場景:

  1. 加載特定路徑的 class 檔案

  2. 熱部署加載 class 檔案

  3. 從網絡中加載一個加密的 class 檔案

Java面試題(二):你真的懂這幾道題了嗎?

1、能否創建一個包含可變物件的不可變物件?

雖然很多人聽說過不可變物件,但你不一定懂,以及知道怎麼創建。

不可變物件:不可變物件(Immutable Objects)即物件一旦被創建它的狀態(物件的資料,也即物件屬性值)就不能改變,任何對它的改變都應該產生一個新的物件

如何創建不可變類?

可以遵照以下幾點來編寫一個不可變類:

A. 確保類不能被繼承:將類宣告為final, 或者使用靜態工廠並宣告建構式為private。。

B. 確保物件的屬性不能被修改:可以使用private和final修飾符來修飾該類的屬性,以確保不被修改。

C. 不要提供任何可以修改物件狀態的方法。

不過,如果物件的屬性是一個可變物件,則需要特別註意,例如對於下麵這個:

1public final class ImmutableDemo {  
2    private final int[] myArray;  
3    public ImmutableDemo(int[] array) {  
4        this.myArray = array// 錯了 
5    }  
6}

雖然屬性宣告為 final 了,但是 array 是一個取用,別人是可以在外部改變這個陣列的值的,進而 myArray 所指向的物件就被改變了。

因為,如果屬性是一個可變物件,我們應該採用克隆的方式。如下:

1public final class MyImmutableDemo {  
2    private final int[] myArray;  
3    public MyImmutableDemo(int[] array) {  
4        this.myArray = array.clone();   
5    }   
6}

不過,最好是採用深度克隆比較好。

問題的答案

說了這麼多,相信你也知道這道題的答案了,答是可以包含可變物件的,只是,我們要保證這個物件的狀態不能被改變。

2. 談談對java多型的理解

多型的功能:允許不同類物件對同一訊息做出響應,即同一訊息可以根據發送物件的實際型別的不同而呼叫不同的方法。

這種根據實際型別來呼叫不可方法的技術也稱之為動態系結

多型其中的一個應用該就是方法的重寫了。關於方法的重寫,如果想更加深入瞭解可以看我這篇文章:從jvm角度看懂類初始化、方法多載、重寫。

多型的一些優點總結:

  • 可替換性:多型對已存在代碼具有可替換性

  • 可擴充性:增加新的子類不影響已經存在的類結構

  • 接口性:多型是超類通過方法簽名,向子類提供一個公共接口,由子類來完善或者重寫它來實現的。

  • 靈活性

  • 簡化性

說實話,這些優點我是直接去抄過來的,看著好死,哈哈。

3. 5、final,finally,finalize的區別

1、final 可以用來修改類、方法、變數。被 final 修改的類不可以被繼承,變數不可以修改,方法則不可以重寫。

2、finally 則是 Java 保證重點代碼一定要被執行的一種機制。例如我們可以用 try-finally 或者 try-catch-finally 類進行檔案的關閉、JDBC的關閉等。

3、finalize 是 Object 的一個方法,它會在物件被回收之前被呼叫。我們可以用這個方法等物件要被回收的時候,來完成一些特定的任務。不過需要註意的是,finalize 機制已經不推薦使用了,並且在 JDK9 開始被標記為 deprecated 了。

拓展

a. 對於下麵這段代碼

1    try {
2        System.exit(1);
3    }finally {
4        System.out.println("猜一下我會不會執行");
5    }

你覺得 finally 裡面的代碼會被執行嗎?

答是不會的,因為System.exit(1)表達程式非正常退出,註意,非正常,也就是說,執行了這個陳述句,程式就要退出了。

b. 註意,被 final 修飾的變數,只是值這個變數不能在被賦值了,變數所指向的物件還是可以改變的。例如:

1    final List list =  new ArrayList<>();
2    list.add(1);
3    list.add(2);
4    System.out.println(list.toString());

打印結果

1[12]

list 所執行的物件還是可以改變滴,只是這個變數本身不能在被賦值,所以我們也經常把被 final 修飾的變數稱之為常量


●編號903,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

程式員求職面試

更多推薦25個技術類公眾微信

涵蓋:程式人生、演算法與資料結構、黑客技術與網絡安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。