Java開發基礎常見面試題及答案彙總(實用乾貨!)

牛仔碼農 發表於 2021-10-09
Java 面試

Java開發面試題及答案

今天抽空來整理整理Java開發面試中的那點事兒吧,幫助那些正在找工作或想跳槽找工作的兄弟姐妹們!

分享目前Java開發常見的面試問題以及問題的答案給大家閱讀參考。

Java基礎學習資料點下邊!!!

Java完整學習資料

1、String類可以被繼承嗎?

String類在宣告時使用final關鍵字修飾,被final關鍵字修飾的類無法被繼承。

接下來我們可以看一下String類的原始碼片段:

public final class String
    implements java.io.Serializable, Comparable<String>,CharSequence {
    /** The value is used for character storage. */
    private final char value[];
 
    /** Cache the hash code for the string */
    private int hash; // Default to 0
 
    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

● 為什麼Java語言的開發者,把String類定義為final的呢?

因為只有當字串是不可變的,字串池才有可能實現。字串池的實現可以在執行時節約很多heap空間,因為不同的字串變數都指向池中的同一個字串。但如果字串是可變的,那麼String interning將不能實現,因為這樣的話,如果變數改變了它的值,那麼其它指向這個值的變數的值也會一起改變。如果字串是可變的,那麼會引起很嚴重的安全問題。譬如,資料庫的使用者名稱、密碼都是以字串的形式傳入來獲得資料庫的連線,或者在socket程式設計中,主機名和埠都是以字串的形式傳入。因為字串是不可變的,所以它的值是不可改變的,否則黑客們可以鑽到空子,改變字串指向的物件的值,造成安全漏洞。

因為字串是不可變的,所以是多執行緒安全的,同一個字串例項可以被多個執行緒共享。這樣便不用因為執行緒安全問題而使用同步。字串自己便是執行緒安全的。

因為字串是不可變的,所以在它建立的時候HashCode就被快取了,不需要重新計算。這就使得字串很適合作為Map中的鍵,字串的處理速度要快過其它的鍵物件。這就是HashMap中的鍵往往都使用字串。

● final關鍵字除了修飾類之外,還有哪些用法呢?

  • final修飾的變數,一旦賦值,不可重新賦值;
  • final修飾的方法無法被覆蓋;
  • final修飾的例項變數,必須手動賦值,不能採用系統預設值;
  • final修飾的例項變數,一般和static聯用,用來宣告常量;

注意:final不能和abstract關鍵字聯合使用。
總之,final表示最終的、不可變的。

2、& 和 && 的區別?

● &運算子是:邏輯與;&&運算子是:短路與。

● &和&&在程式中最終的運算結果是完全一致的,只不過&&存在短路現象,當&&運算子左邊的表示式結果為false的時候,右邊的表示式不執行,此時就發生了短路現象。如果是&運算子,那麼不管左邊的表示式是true還是false,右邊表示式是一定會執行的。這就是他們倆的本質區別。

● 當然,&運算子還可以使用在二進位制位運算上,例如按位與操作。

image.png

3、兩個物件值相同equals結果為true,但卻可有不同的 hashCode,這句話對不對?

不對,如果兩個物件x和y滿足x.equals(y) == true,它們的雜湊值(hashCode)應當相同。Java 對於equals方法和hashCode方法是這樣規定的:

(1)如果兩個物件相同(equals方法返回true),那麼它們的hashCode值一定要相同;

(2)如果兩個物件的 hashCode相同,它們並不一定相同。當然,你未必按照要求去做,但是如果你違背了上述原則就會發現在使用集合時,相同的物件可以出現在Set 集合中,同時增加新元素的效率會大大降低(對於使用雜湊儲存的系統,如果雜湊碼頻繁的衝突將會造成存取效能急劇下降)。

關於equals和hashCode方法,很多Java程式設計師都知道,但很多人也就是僅僅瞭解而已,在Joshua Bloch的大作《Effective Java》(《Effective Java》在很多公司,是Java程式設計師必看書籍,如果你還沒看過,那就趕緊去買一本吧)中是這樣介紹 equals 方法的:

首先equals方法必須滿足自反性(x.equals(x)必須返回true)、對稱性(x.equals(y)返回true 時,y.equals(x)也必須返回true)、傳遞性(x.equals(y)和y.equals(z)都返回true時,x.equals(z)也必須返回true)和一致性(當x和y引用的物件資訊沒有被修改時,多次呼叫x.equals(y)應該得到同樣的返回值),而且對於任何非null值的引用x,x.equals(null)必須返回false。

實現高質量的equals方法的訣竅包括:

  • 使用==操作符檢查"引數是否為這個物件的引用";
  • 使用 instanceof操作符檢查"引數是否為正確的型別";
  • 對於類中的關鍵屬性,檢查引數傳入物件的屬性是否與之相匹配;
  • 編寫完equals方法後,問自己它是否滿足對稱性、傳遞性、一致性;
  • 重寫equals時總是要重寫hashCode;
  • 不要將equals方法引數中的Object物件替換為其他的型別,在重寫時不要忘掉@Override註解。

4、在 Java 中,如何跳出當前的多重巢狀迴圈?

在最外層迴圈前加一個標記如outfor,然後用break outfor;可以跳出多重迴圈。

例如以下程式碼:

public class TestBreak {
    public static void main(String[] args) {
        outfor: for (int i = 0; i < 10; i++){
            for (int j = 0; j < 10; j++){
                if (j == 5){
                    break outfor;
                }
                System.out.println("j = " + j);
            }
        }
    }
}

執行結果如下所示:

j = 0
j = 1
j = 2
j = 3
j = 4

5、過載(overload)和重寫(override)的區別?過載的方法能否根據返回型別進行區分?

方法的過載和重寫都是實現多型的方式,區別在於前者實現的是編譯時的多型性,而後者實現的是執行時的多型性。

過載發生在一個類中,同名的方法如果有不同的引數列表(型別不同、個數不同、順序不同)則視為過載。

重寫發生在子類與父類之間,重寫要求子類重寫之後的方法與父類被重寫方法有相同的返回型別,比父類被重寫方法更好訪問,不能比父類被重寫方法宣告更多的異常(里氏代換原則)。過載對返回型別沒有特殊的要求。

● 方法過載的規則:

  • 方法名一致,引數列表中引數的順序,型別,個數不同。
  • 過載與方法的返回值無關,存在於父類和子類,同類中。
  • 可以丟擲不同的異常,可以有不同修飾符。

● 方法重寫的規則:

  • 引數列表、方法名、返回值型別必須完全一致;
  • 構造方法不能被重寫;
  • 宣告為 final 的方法不能被重寫;
  • 宣告為 static 的方法不存在重寫(重寫和多型聯合才有意義);
  • 訪問許可權不能比父類更低;
  • 重寫之後的方法不能丟擲更寬泛的異常;

6、當一個物件被當作引數傳遞到一個方法後,此方法可改變這個物件的屬性,並可返回變化後的結果,那麼這裡是值傳遞還是引用傳遞?

是值傳遞。Java 語言的方法呼叫只支援引數的值傳遞。當一個物件例項作為一個引數被傳遞到方法中時,引數的值就是對該物件的記憶體地址。這個值(記憶體地址)被傳遞後,同一個記憶體地址指向堆記憶體當中的同一個物件,所以通過哪個引用去操作這個物件,物件的屬性都是改變的。

7、為什麼方法不能根據返回型別來區分過載?

我們來看以下的程式碼:

public void testMethod(){
    doSome();
}
public void doSome(){
    
}
public int doSome(){
    return 1;
}

在Java語言中,呼叫一個方法,即使這個方法有返回值,我們也可以不接收這個返回值,例如以上兩個方法doSome(),在testMethod()中呼叫的時候,Java編譯器無法區分呼叫的具體是哪個方法。所以對於編譯器來說,doSome()方法不是過載而是重複了,編譯器報錯。所以區分這兩個方法不能依靠方法的返回值型別。

8、抽象類(abstract class)和介面(interface)有什麼異同?

不同點:

● 抽象類中可以定義構造器,介面不能;

● 抽象類可以有抽象方法和具體方法,介面不能有具體方法;

● 介面中的成員全都是 public 的,抽象類中的成員可以使用private、public、protected、預設等修飾;

● 抽象類中可以定義成員變數,介面中只能是常量;

● 有抽象方法的類必須被宣告為抽象類,而抽象類未必要有抽象方法;

● 抽象類中可以包含靜態方法,介面中不能有靜態方法;

● 一個類只能繼承一個抽象類,一個類可以實現多個介面;

相同點:

● 不能夠例項化;

● 可以將抽象類和介面型別作為引用型別;

● 一個類如果繼承了某個抽象類或者實現了某個介面都需要對其中的抽象方法全部進行實現,否則該類仍然需要被宣告為抽象類;

9、char 型變數中能不能儲存一箇中文漢字,為什麼?

char 型別可以儲存一箇中文漢字,因為Java中使用的編碼是Unicode(不選擇任何特定的編碼,直接使用字元在字符集中的編號,這是統一的唯一方法),一個char 型別佔2個位元組(16 位元),所以放一箇中文是沒問題的。

補充:使用Unicode 意味著字元在JVM內部和外部有不同的表現形式,在JVM內部都是 Unicode,當這個字元被從JVM內部轉移到外部時(例如存入檔案系統中),需要進行編碼轉換。所以 Java 中有位元組流和字元流,以及在字元流和位元組流之間進行轉換的轉換流,如 InputStreamReader和OutputStreamReader,這兩個類是位元組流和字元流之間的介面卡類,承擔了編碼轉換的任務。

10、抽象的(abstract)方法是否可同時是靜態的(static), 是否可同時是本地方法(native),是否可同時被 synchronized?

都不能。

● 抽象方法需要子類重寫,而靜態的方法是無法被重寫的,因此二者是矛盾的。

● 本地方法是由原生程式碼(如 C++ 程式碼)實現的方法,而抽象方法是沒有實現的,也是矛盾的。

● synchronized 和方法的實現細節有關,抽象方法不涉及實現細節,因此也是相互矛盾的。

11、==和equals的區別?

equals和==最大的區別是一個是方法一個是運算子。

● ==:如果比較的物件是基本資料型別,則比較的是數值是否相等;如果比較的是引用資料型別,則比較的是物件的地址值是否相等。

● equals():用來比較方法兩個物件的內容是否相等。equals方法不能用於基本資料型別的變數,如果沒有對equals方法進行重寫,則比較的是引用型別的變數所指向的物件的地址。

12、闡述靜態變數和例項變數的區別?

不管建立多少個物件,靜態變數在記憶體中有且僅有一個;例項變數必須依存於某一例項,需要先建立物件然後通過物件才能訪問到它。靜態變數可以實現讓多個物件共享記憶體。

13、break和continue的區別?

● break和continue 都是用來控制迴圈的語句。

● break 用於完全結束一個迴圈,跳出迴圈體執行迴圈後面的語句。

continue 用於跳過本次迴圈,繼續下次迴圈。

14、String s = "Hello";s = s + " world!";這兩行程式碼執行後,原始的 String 物件中的內容變了沒有?

沒有。

因為 String被設計成不可變類,所以它的所有物件都是不可變物件。

在這段程式碼中,s原先指向一個 String 物件,內容是 "Hello",然後我們對 s 進行了“+”操作,那麼 s 所指向的那個物件是否發生了改變呢?

答案是沒有。這時s不指向原來那個物件了,而指向了另一個 String 物件,內容為"Hello world!",原來那個物件還存在於記憶體之中,只是 s 這個引用變數不再指向它了。

通過上面的說明,我們很容易匯出另一個結論,如果經常對字串進行各種各樣的修改,或者說,不可預見的修改,那麼使用 String 來代表字串的話會引起很大的記憶體開銷。因為 String 物件建立之後不能再改變,所以對於每一個不同的字串,都需要一個 String 物件來表示。這時,應該考慮使用
StringBuffer/StringBuilder類,它允許修改,而不是每個不同的字串都要生成一個新的物件。並且,這兩種類的物件轉換十分容易。同時,我們還可以知道,如果要使用內容相同的字串,不必每次都 new 一個 String。例如我們要在構造器中對一個名叫 s 的 String 引用變數進行初始化,把它設定為初始值,應當這樣做:

s = new String("動力節點,口口相傳的Java黃埔軍校");

而不是這樣做:

s = new String("動力節點,口口相傳的Java黃埔軍校");

後者每次都會呼叫構造器,生成新物件,效能低下且記憶體開銷大,並且沒有意義,因為 String 物件不可改變,所以對於內容相同的字串,只要一個 String 物件來表示就可以了。也就說,多次呼叫上面的構造器建立多個物件,他們的 String 型別屬性 s 都指向同一個物件。

上面的結論還基於這樣一個事實:對於字串常量,如果內容相同,Java 認為它們代表同一個 String 物件。而用關鍵字 new 呼叫構造器,總是會建立一個新的物件,無論內容是否相同。

至於為什麼要把 String 類設計成不可變類,是它的用途決定的。其實不只String,很多 Java 標準類庫中的類都是不可變的。在開發一個系統的時候,我們有時候也需要設計不可變類,來傳遞一組相關的值,這也是物件導向思想的體現。不可變類有一些優點,比如因為它的物件是隻讀的,所以多執行緒併發訪問也不會有任何問題。當然也有一些缺點,比如每個不同的狀態都要一個物件來代表,可能會造成效能上的問題。所以 Java 標準類庫還提供了一個可變版本,即 StringBuffer。