1. 程式人生 > 實用技巧 >【面試】Java基礎05

【面試】Java基礎05

【面試】Java基礎05

針對網上提出的常見的Java基礎面試題,在此做下學習筆記,方便後續複習檢視:

注:有些回答可能忘記標出參考出處,侵權請聯絡刪除:-)

  1. final 修飾 StringBuffer 後還可以 append 嗎?
  2. Object 的常用方法有哪些?
  3. final、finally、finalize 的區別?
  4. finally 塊中的程式碼什麼時候被執行?是不是一定會被執行到?
  5. try-catch-finally 中,如果 catch 中 return 了,finally 還會執行嗎?try-catch-finally 中那個部分可以省略?

21. final 修飾 StringBuffer 後還可以 append 嗎?

​ 可以。final 修飾的是一個引用變數,那麼這個引用始終只能指向這個物件,但是這個物件內部的屬性是可以變化的

官方文件解釋:

once a final variable has been assigned, it always contains the same value. If a final variable holds a reference to an object, then the state of the object may be changed by operations on the object, but the variable will always refer to the same object.

一旦一個final修飾的變數被賦值了,它總是包含相同的值。如果一個final變數持有對一個物件的引用,那麼該物件的狀態可以通過對該物件的操作而改變,但該變數將始終引用同一個物件。

參考:https://mp.weixin.qq.com/s/4E3xRXOVUQzccmP0yahlqA

22. Object 的常用方法有哪些?

  • clone 方法:用於建立並返回當前物件的一份拷貝;
  • getClass 方法:用於返回當前執行時物件的 Class;
  • toString 方法:返回物件的字串表示形式;
  • finalize 方法:例項被垃圾回收器回收時觸發的方法;
  • equals 方法:用於比較兩個物件的記憶體地址是否相等,一般需要重寫;
  • hashCode 方法:用於返回物件的雜湊值;
  • notify 方法:喚醒一個在此物件監視器上等待的執行緒。如果有多個執行緒在等待只會喚醒一個。
  • notifyAll 方法:作用跟 notify() 一樣,只不過會喚醒在此物件監視器上等待的所有執行緒,而不是一個執行緒。
  • wait 方法:讓當前物件等待;
  • .......

23. final、finally、finalize 的區別?

final:final可以用來修飾類,方法和變數(成員變數或區域性變數)

  1. 修飾類:當用final修飾類的時,表明該類不能被其他類所繼承。(final類中所有的成員方法都會隱式的定義為final方法)
  2. 修飾方法:把方法鎖定,以防止繼承類對其進行更改。
  3. 修飾變數:final成員變量表示常量,只能被賦值一次,賦值後其值不再改變。類似於C++中的const。
    1. 當final修飾一個基本資料型別時,表示該基本資料型別的值一旦在初始化後便不能發生變化;
    2. 如果final修飾一個引用型別時,則在對其初始化之後便不能再讓其指向其他物件了,但該引用所指向的物件的內容是可以發生變化的。
    3. 本質上是一回事,因為引用的值是一個地址,final要求值,即地址的值不發生變化。
    4. final修飾一個成員變數(屬性),必須要顯示初始化。這裡有兩種初始化方式,一種是在變數宣告的時候初始化;第二種方法是在宣告變數的時候不賦初值,但是要在這個變數所在的類的所有的建構函式中對這個變數賦初值。

finally:finally作為異常處理的一部分,它只能用在try/catch語句中,並且附帶一個語句塊,表示這段語句最終一定會被執行(不管有沒有丟擲異常),經常被用在需要釋放資源的情況下。

其實不然,存在finally語句不被執行的情況,見24.

finalize:Object類的一個方法,在垃圾回收時會呼叫被回收物件的finalize。

參考:https://www.cnblogs.com/ktao/p/8586966.html

24. finally 塊中的程式碼什麼時候被執行?是不是一定會被執行到?

finally 塊中的程式碼什麼時候被執行?

在 Java 語言的異常處理中,finally 塊的作用就是為了保證無論出現什麼情況,finally 塊裡的程式碼一定會被執行。由於程式執行 return 就意味著結束對當前函式的呼叫並跳出這個函式體,因此任何語句要執行都只能在 return 前執行(除非碰到 exit 函式),因此 finally 塊裡的程式碼也是在 return 之前執行的

​ 此外,如果 try-finally 或者 catch-finally 中都有 return,那麼 finally 塊中的 return 將會覆蓋別處的 return 語句,最終返回到呼叫者那裡的是 finally 中 return 的值。

從位元組碼角度對其進行說明:

先看下finally在位元組碼中的體現:

public class Demo3_11_4 {
 public static void main(String[] args) {
     int i = 0;
     try {
         i = 10;
     } catch (Exception e) {
         i = 20;
     } finally {
         i = 30;
     }
 }
}

位元組碼:

Code:
stack=1, locals=4, args_size=1
  0: iconst_0
  1: istore_1          // 0 -> i
  2: bipush        10  // try --------------------------------------
  4: istore_1          // 10 -> i                                  |
  5: bipush        30  // finally                                  |
  7: istore_1          // 30 -> i                                  |
  8: goto          27  // return -----------------------------------
 11: astore_2          // catch Exceptin -> e ----------------------
 12: bipush        20  //                                          |
 14: istore_1          // 20 -> i                                  |
 15: bipush        30  // finally                                  |
 17: istore_1		  // 30 -> i                                  |
 18: goto          27  // return -----------------------------------
 21: astore_3          // catch any -> slot 3 ----------------------
 22: bipush        30  // finally                                  |
 24: istore_1          // 30 -> i                                  |
 25: aload_3           // <- slot 3  有這個slot3                    |
 26: athrow            // throw 丟擲---------------------------------
 27: return
Exception table:
  from    to  target type
      2     5    11   Class java/lang/Exception
      2     5    21   any  // 剩餘的異常型別,比如 Error,監測[2, 5)中的其他異常/error
     11    15    21   any  // 剩餘的異常型別,比如 Error,監測[11, 15)->catch塊中的其他異常/error
LocalVariableTable:
 Start  Length  Slot  Name   Signature
    12       3     2     e   Ljava/lang/Exception;
     0      28     0  args   [Ljava/lang/String;
     2      26     1     i   I

可以看到 finally 中的程式碼被複制了 3 份,分別放入 try 流程,catch 流程以及 catch 剩餘的異常型別流程

而當finally程式碼塊中出現了return

public class Demo3_12_1 {
 public static void main(String[] args) {
     int result = test();
     System.out.println(result);  // 20
 }

 public static int test() {
     try {
         return 10;
     } finally {
         return 20;
     }
 }
}

位元組碼:

public static int test();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
 stack=1, locals=2, args_size=0
    0: bipush        10  // <- 10 放入棧頂
    2: istore_0          // 10 -> slot 0(從棧頂移除了)
    3: bipush        20  // < - 20 放入棧頂
    5: ireturn           // 返回棧頂 int(20)
    6: astore_1          // catch any -> slot 1
    7: bipush        20  // <- 20 放入棧頂
    9: ireturn           // 返回棧頂 int(20)
 Exception table:
    from    to  target type
        0     3     6   any
  • 由於 finally 中的 ireturn 被插入了所有可能的流程,因此返回結果肯定以 finally 的為準
  • 至於位元組碼中第 2 行,似乎沒啥用,且留個伏筆,看下個例子:Demo3_12_2
  • 跟上例中的 finally 相比,發現沒有 athrow 了,這告訴我們:如果在 finally 中出現了 return,會吞掉異常!可以試一下下面的程式碼
public class Demo3_12_1 {
   public static void main(String[] args) {
       int result = test();
       System.out.println(result);  // 20
   }

   public static int test() {
       try {
           int i = 1/0;  // 加不加結果都一樣
           return 10;
       } finally {
           return 20;
       }
   }
}

很危險的操作,異常被吞掉了,即使發生了異常也不知道了

finally 對返回值影響

public class Demo3_12_2 {
 public static void main(String[] args) {
     int result = test();
     System.out.println(result);  // 10
 }

 public static int test() {
     int i = 10;
     try {
         return i;
     } finally {
         i = 20;
     }
 }
}

位元組碼:

public static int test();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
 stack=1, locals=3, args_size=0
    0: bipush        10  // <- 10 放入棧頂
    2: istore_0			// 10 -> i
    3: iload_0			// <- i(10)
    4: istore_1			// 10 -> slot1,暫存至slot1,目的是為了固定返回值 ☆
    5: bipush        20  // <- 20 放入棧頂
    7: istore_0          // 20 -> i
    8: iload_1		    // <- slot 1(10) 載入 slot 1 暫存的值 ☆
    9: ireturn           // 返回棧頂的 int(10)
   10: astore_2          // 期間出現異常,儲存異常到slot2中
   11: bipush        20  // <- 20 放入棧頂
   13: istore_0          // 20 -> i
   14: aload_2           // 異常載入進棧
   15: athrow            // athrow出異常
 Exception table:
    from    to  target type
        3     5    10   any

參考:

https://www.bilibili.com/video/BV1yE411Z7AP?p=126 ~ https://www.bilibili.com/video/BV1yE411Z7AP?p=128

finally 是不是一定會被執行到?

不一定。下面列舉執行不到的情況:

  • 當程式進入 try 塊之前就出現異常時,會直接結束,不會執行 finally 塊中的程式碼;

    @Test
    public void test08(){
        int i = 1;
        i = i/0;
        try {
            System.out.println("try");
        }finally {
            System.out.println("finally");
        }
    }
    
  • 當程式在 try 塊中強制退出時也不會去執行 finally 塊中的程式碼,比如在 try 塊中執行 exit 方法

    public void test08(){
        int i = 1;
        try {
            System.out.println("try");
            System.exit(0);
        }finally {
            System.out.println("finally");
        }
    }
    

參考:https://mp.weixin.qq.com/s/4E3xRXOVUQzccmP0yahlqA

25. try-catch-finally 中,如果 catch 中 return 了,finally 還會執行嗎?try-catch-finally 中那個部分可以省略?

try-catch-finally 中,如果 catch 中 return 了,finally 還會執行嗎?

會。程式在執行到 return 時會首先將返回值儲存在一個指定的位置(23中,對finally的位元組碼中可以體現),其次去執行 finally 塊,最後再返回。

因此,對基本資料型別,在 finally 塊中改變 return 的值沒有任何影響,直接覆蓋掉;

// 下述程式碼返回的結果為1
public static int test(){
    String str = "1.1";
    int i = 0;
    try {
        System.out.println("try");
        int j = 1/0;
    }catch (Exception e){
        e.printStackTrace();
        System.out.println("catch");
        return ++i;
    }finally {
        System.out.println("finally");
        return i;
    }
}

而對引用型別是有影響的,返回的是在 finally 對 前面 return 語句返回物件的修改值

// // 下述程式碼返回的結果為def
public static String test02(){
    String str = "abc";
    try {
        System.out.println("try");
        int j = 1/0;
    }catch (Exception e){
        e.printStackTrace();
        System.out.println("catch");
        return str = "def";
    }finally {
        System.out.println("finally");
        return str;
    }
}

try-catch-finally 中那個部分可以省略?

  1. catch 可以省略。try 只適合處理執行時異常,try+catch 適合處理執行時異常+普通異常。也就是說,如果你只用 try 去處理普通異常卻不加以 catch 處理,編譯是通不過的,因為編譯器硬性規定,普通異常如果選擇捕獲,則必須用 catch 顯示宣告以便進一步處理。而執行時異常在編譯時沒有如此規定,所以 catch 可以省略,你加上catch 編譯器也覺得無可厚非。

    @Test
    public void test11(){
        try {
            int i = 1/0;
        }finally {
            System.out.println("finally");
            return;
        }
    }
    
  2. finally也可以省略

    @Test
    public void test11(){
        try {
            int i = 1/0;
        }catch (ArithmeticException e){
            e.printStackTrace();
        }
    }