1. 程式人生 > 其它 >【JAVA】基礎知識面試題梳理(二)

【JAVA】基礎知識面試題梳理(二)

技術標籤:第二維度 語言java程式語言

1、String、StringBuffer、StringBuilder 的區別?

1、都是final類,都不允許被繼承
2、String長度是不可變的,StringBuffer和StringBuilder長度是可變的。
3、StringBuffer是執行緒安全的,StringBuilder是執行緒不安全的,但他們兩個中的所有方法都是相同的,StringBuffer在StringBuilder的方法之上加了synchronized修飾,保證執行緒安全。
4、StringBuilder比StringBuffer擁有更好的效能。
5、如果一個String型別的字串,在編譯時就可以確定是一個字串常量,則編譯完成之後,字串會自動拼接成一個常量。此時String的速度比StringBuilder和StringBuffer的效能更好。

2、String str=“aaa” 與 String str=new String(“aaa”)一樣嗎?

不同,記憶體分配的方式不同。

String str=“aaa”,建立了1個物件,建立的"aaa"是常量,jvm將其分配在常量池中;
String str=new String(“aaa”),建立了2個物件,一個是在常量池中,一個在堆記憶體中。

3、String str=“aa” ,String s=“bb” ,String aa=aa+s;一共建立了幾個物件?

一共有2個引用,3個物件;

“aa"與"bb"都是常量,常量的值不能改變,當執行字串拼接的時候,會建立一個新的常量"aabb”,將其存到常量池中。

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

沒有。

因為 String 被設計成不可變(immutable)類,所以它的所有物件都是不可變物件。在這段程式碼中,s 原先指向一個 String 物件,內容是 “Hello”,然後我們對 s 進行了“+”操作,那麼 s 所指向的那個物件是沒有發生變化的。這時,s 不指向原來那個物件了,而指向了另一個 String 物件,內容為"Hello world!",原來那個物件還存在於記憶體之中,只是 s 這個引用變數不再指向它了。

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

1 public class Demo {
2   private String s;
3   ...
4   s = "Initial Value";
5   ...
6 }

而非s = new String(“Initial Value”);

後者每次都會呼叫構造器,生成新物件,效能低下且記憶體開銷大,並且沒有意義,因為 String 物件不可改變,所以對於內容相同的字串,只要一個 String 物件來表示就可以了。也就說,多次呼叫上面的構造器建立多個物件,他們的 String 型別屬性 s 都指向同一個物件。
上面的結論還基於這樣一個事實
對於字串常量,如果內容相同,Java 認為它們代表同一個 String 物件。
而用關鍵字 new 呼叫構造器,總是會建立一個新的物件,無論內容是否相同。
至於為什麼要把 String 類設計成不可變類,是它的用途決定的。其實不只 String,很多 Java 標準類庫中的類都是不可變的。在開發一個系統的時候,我們有時候也需要設計不可變類,來傳遞一組相關的值,這也是面向物件思想的體現。
不可變類有一些優點,比如因為它的物件是隻讀的,所以多執行緒併發訪問也不會有任何問題。
當然也有一些缺點,比如每個不同的狀態都要一個物件來代表,可能會造成效能上的問題。所以 Java 標準類庫還提供了一個可變版本,即 StringBuffer。

5、什麼是泛型?

Java泛型( generics)是JDK 5中引⼊的⼀個新特性, 允許在定義類和介面的時候使⽤型別引數( type parameter) 。

宣告的型別引數在使⽤時⽤具體的型別來替換。泛型最主要的應⽤是在JDK 5中的新集合類框架中。

泛型最⼤的好處是可以提⾼程式碼的復⽤性。以List介面為例,我們可以將String、 Integer等型別放⼊List中, 如不⽤泛型, 存放String型別要寫⼀個List介面, 存放Integer要寫另外⼀個List介面, 泛型可以很好的解決這個問題。

6、什麼是型別擦除?

通過型別引數合併,將泛型型別例項關聯到同一份位元組碼上。
編譯器只為泛型型別生成一份位元組碼,並將其例項關聯到這份位元組碼上。

泛型是通過型別擦除來實現的,編譯器在編譯時擦除了所有型別相關的資訊,所以在執行時不存在任何型別相關的資訊。例如 List在執行時僅用一個List來表示。這樣做的目的,是確保能和Java 5之前的版本開發二進位制類庫進行相容。你無法在執行時訪問到型別引數,因為編譯器已經把泛型型別轉換成了原始型別。

型別擦除的主要過程如下:
1.將所有的泛型引數用其最左邊界(最頂級的父型別)型別替換。
2.移除所有的型別引數。

7、什麼是泛型中的限定萬用字元和非限定萬用字元 ?

限定萬用字元:
對型別進行了限制。有兩種限定萬用字元:
一種是 它通過確保型別必須是T的子類來設定型別的上界,
另一種是它通過確保型別必須是T的父類來設定型別的下界。
泛型型別必須用限定內的型別來進行初始化,否則會導致編譯錯誤。

非限定萬用字元:
表示了非限定萬用字元,因為可以用任意型別來替代。

8、泛型中K T V E ?object等的含義 ?

E - Element (在集合中使用,因為集合中存放的是元素)
T - Type(Java 類)
K - Key(鍵)
V - Value(值)
N - Number(數值型別)
?- 表示不確定的java型別(無限制萬用字元型別)
Object - 是所有類的根類,任何類的物件都可以設定給該Object引用變數,使用的時候可能需要型別強制轉換,但是用使用了泛型T、E等這些識別符號後,在實際用之前型別就已經確定了,不需要再進行型別強制轉換。

9、說一下Java中的異常體系?

在這裡插入圖片描述

10、Error和Exception的區別?

Error(錯誤):
系統中的錯誤,是在程式編譯時出現的錯誤,只能通過修改程式才能修正。一般是指與虛擬機器相關的問題,如系統崩潰,虛擬機器錯誤,記憶體空間不足,方法呼叫棧溢等。
Exception(異常):
表示程式可以處理的異常,可以捕獲且可能恢復。遇到這類異常,應該儘可能處理異常,使程式恢復執行,而不應該隨意終止異常。

11、寫出你最常見的 5 個 RuntimeException?

(1)java.lang.NullPointerException 空指標異常;出現原因:呼叫了未經初始化的物件或者是不存在的物件。
(2)java.lang.ClassNotFoundException 指定的類找不到;出現原因:類的名稱和路徑載入錯誤;通常都是程式
試圖通過字串來載入某個類時可能引發異常。
(3)java.lang.NumberFormatException 字串轉換為數字異常;出現原因:字元型資料中包含非數字型字元。
(4)java.lang.IndexOutOfBoundsException 陣列角標越界異常,常見於運算元組物件時發生。
(5)java.lang.IllegalArgumentException 方法傳遞引數錯誤。
(6)java.lang.ClassCastException 資料型別轉換異常。

12、try()裡面有⼀個return語句, 那麼後面的finally{}裡面的程式碼會不會被執行?什麼時候執行,return前還是return後?

如果try中有return語句, 那麼finally中的程式碼還是會執⾏。
因為return表示的是要整個方法體返回, 所以,finally中的語句會在return之前執⾏。
但是return前執行的finally塊內,對資料的修改效果對於引用型別和值型別會所不同:

// 測試 修改值型別
 3  static int f() {
 5    int ret = 0;
 7    try {
 9        return ret;  // 返回 0,finally內的修改效果不起作用
11    } finally {
13        ret++;
15        System.out.println("finally執行")
17    }
18 }
22
23  // 測試 修改引用型別
25  static int[] f2(){
27    int[] ret = new int[]{0};
29    try {
31        return ret;  // 返回 [1],finally內的修改效果起了作用
33    } finally {
35        ret[0]++;
37        System.out.println("finally執行");
39    }
40 }

13、呼叫下面的方法,得到的返回值是什麼?

     public int getNum() {
         try {
             int a = 1 / 0;
             return 1;
         } catch (Exception e) {
            return 2;
        } finally {
           return 3;
        }
    }

返回值為3。

程式碼在走到第 3 行的時候遇到了一個 MathException,這時第四行的程式碼就不會執行了,程式碼直接跳轉到 catch語句中,走到第 6 行的時候,異常機制有這麼一個原則如果在 catch 中遇到了 return 或者異常等能使該函式終止的話那麼有 finally 就必須先執行完 finally 程式碼塊裡面的程式碼然後再返回值。因此程式碼又跳到第 8 行,可惜第 8 行是一個return 語句,那麼這個時候方法就結束了,因此第 6 行的返回結果就無法被真正返回。如果 finally 僅僅是處理了一個釋放資源的操作,那麼該道題最終返回的結果就是 2。因此上面返回值是 3。

14、throw 和 throws 的區別?

throw:
throw 語句用在方法體內,表示丟擲異常,由方法體內的語句處理。
throw 是具體向外丟擲異常的動作,所以它丟擲的是一個異常例項,執行 throw 一定是丟擲了某種異常。
throws:
throws 語句是用在方法聲明後面,表示如果丟擲異常,由該方法的呼叫者來進行異常的處理。
throws 主要是宣告這個方法會丟擲某種型別的異常,讓它的使用者要知道需要捕獲的異常的型別。
throws 表示出現異常的一種可能性,並不一定會發生這種異常。