位元組跳動技術整理:46道面試題帶你瞭解中高階Java面試
異常處理
將程式執行中發生的不正常情況稱為"異常"。(語法錯誤和邏輯錯誤不是異常)
異常分為兩類:
- Error:Java虛擬機器無法解決的嚴重問題。如:JVM系統內部錯誤、資源耗盡的嚴重情況。比如:StackOverflow和OOM(OutOfMemory)。一般不編寫針對性程式碼的程式碼進行處理。
- Exception:其他因程式設計錯誤或偶然的外在因素導致的一般性問題,可以使用針對性的程式碼進行處理。例如:
- 空指標
- 試圖讀取不存在的檔案
- 網路連線中斷
- 陣列下標越界
遇到錯誤的解決方法:
- 遇到錯誤就終止程式的執行
- 由程式設計師在編寫程式時,就考慮到錯誤的檢測、錯誤資訊的提示、以及錯誤處理。
異常分為:
- 編譯時異常
- 執行時異常
Java異常的體系結構
- java.lang.Throwable
- java.lang.Error:一般不編寫針對性程式碼處理
- java.lang.Exception:
- 編譯時異常(checked):
- IOException
- FileNotFoundException
- ClassNotFound
- IOException
- 執行時異常(unchecked):RunTimeException
- NullPointerException
- ArrayIndexOutOfBoundsException
- ClassCastException
- NumberFormatException
- InputMismatchException
- ArithmeticException
- 編譯時異常(checked):
執行時異常
面試題:常見的異常有哪些?舉例說明
NullPointerException
String str = null;
//System.out.println(str); 就是null
System.out.println(str.charAt(0));
//所以NullPointerException需要一個為null的物件,且呼叫其方法
IndexOutOfBoundsException
int[] a = new int[2]; System.out.println(a[0]); //未賦值,為0 System.out.println(a[2]); //ArrayIndexOutOfBoundsException String a = "ab"; System.out.println(a.charAt(2)); //StringIndexOutOfBoundsException
ClassCastException
ClassCastException是JVM在檢測到兩個型別間轉換不相容時引發的執行時異常。
Object obj = new Date();
String str = (String)obj;
NumberFormatException
java.lang.NumberFormatException 數字格式異常。當試圖將一個String轉換為指定的數字型別,而該字串卻不滿足數字型別要求的格式時,丟擲該異常。
String a = "123";
String b = "abc";
int a1 = Integer.parseInt(a); //可以正常轉換
int a2 = Integer.parseInt(b); //拋異常
InputMismatchException
InputMismatchException異常是輸入不匹配異常,即輸入的值資料型別與設定的值資料型別不能匹配。
Scanner scanner = new Scanner(System.in);
int value = scanner.nextInt();
System.out.println(value);
//這裡控制檯輸出a
ArithmeticException
ArithmeticException異常是算術運算異常。
int a = 10;
int b = 0;
System.out.println(a / b);
編譯時異常
以FileNotFoundException為例:
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.print(data);
data = fis.read();
}
fis.close();
//這裡需要在方法上加上throws IOException,不然直接報紅。
編譯時異常需要throws出異常才能夠正常使用,執行時異常則不需要。
異常處理機制
Java採用的異常處理機制,是將異常處理的程式程式碼集中在一起,與正常的程式程式碼分開,使得程式簡潔、優雅,並易於維護。
異常處理,抓拋模型:
-
過程一,拋:
- 在程式執行過程中,一旦出現異常,JVM就會自動生成一個對應異常類的物件,並將此物件丟擲。一旦丟擲物件之後,其後的程式碼就不再執行。
- 手動throw一個異常類物件
-
過程二,抓:可以理解為異常處理方式:
- try-catch-finally
- trows
try-catch-finally
try-catch-finally語法:
try{
//可能出現異常的程式碼
}catch(異常型別1 變數名){
//處理異常型別的方式1
}catch(異常型別2 變數名){
//處理異常型別的方式2
}catch(異常型別3 變數名){
//處理異常型別的方式3
}
...
finally{//finally是可選的
//一定會執行的程式碼
}
以空指標異常為例:
String str = null;
try{
System.out.println(str.charAt(0));
}catch (NullPointerException e){
System.out.println(e.getMessage());
e.printStackTrace();
//System.out.println("Attention: Null pointer!");
}catch (Exception e){
System.out.println("Attention: Exception!");
}
//Attention: Null pointer!匹配到NullPointerException就直接結束,不會繼續向下匹配。
-
使用try將可能出現異常的程式碼包裝起來,在執行過程中,一旦出現異常,就會生成一個對應異常類的物件,根據此物件的型別,去catch中進行匹配。
-
一旦try中的異常物件匹配到某一個catch時,就進入catch中進行異常的處理,一旦處理完成,就跳出當前try-catch結構(在沒有寫fianlly的情況下)
-
catch中的異常型別如果沒有子父類關係,則誰宣告在上面,誰宣告在下面無所謂;catch中的異常型別如果滿足子父類關係,則要求子類定義宣告在父類的上面,否則,報錯。
-
常用的異常物件處理方式:
- getMessage()
- printStackTrace():更常用
-
在try結構中宣告的變數,在try外面是不可以訪問的。可以在try語句塊前面宣告變數。
try{
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.print((char)data);
data = fis.read();
}
fis.close();
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
- 使用try-catch-finally處理編譯時異常,使得程式在編譯時就不再報錯,但是執行時仍可能報錯;相當於我們使用try-catch-finally將一個編譯時可能出現的異常,延遲到執行時出現。
finally使用
- finally是可選的。
- finally中宣告的是一定會被執行的程式碼,即使catch中出現了異常,try中有return語句,catch中有return語句等情況。
- 對於物理連線需要自己手動去釋放,JVM是不能自動回收的,比如資料庫連線,輸入流輸出流,Socket連線。此時的資源釋放就需要宣告在finally中。
public void test3(){
FileInputStream fis = null;
try{
File file = new File("hello.txt");
fis = new FileInputStream(file);
int data = fis.read();
while(data != -1){
System.out.print((char)data);
data = fis.read();
}
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}finally {
try {
if(fis != null) //為了避免空指標異常
fis.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
//由於fis是輸出流,需要自己手動釋放資源,所以需要在finally中關閉。
注意:開發中,由於執行時異常比較常見,所以我們通常就不針對執行時異常編寫try-catch-finally。而針對編譯時異常,一定要考慮異常處理。
throws+異常型別
- “throws+異常型別”寫在方法的宣告處,指明此方法執行時,可能會丟擲的異常型別,一旦方法體執行時,出現異常,仍會在異常程式碼處生成一個異常類的物件,此物件滿足throws後的異常型別時,就會被丟擲。異常程式碼後續的程式碼不再被執行。
- try-catch-finally:真正的將異常給處理掉了。
- throws方式:只是將異常拋給了方法的呼叫者,並沒有真正將異常處理掉。
方法重寫
方法重寫的規則之一:
- 子類重寫的方法丟擲的異常型別不大於父類被重寫的方法丟擲的異常型別。(為了正常使用多型)
小結
開發中如何選擇使用try-catch-finally還是使用throws:
- 在父類中都沒有用throws方式處理異常,在子類中肯定不能使用throws方式處理異常;意味著如果子類重寫的方法中有異常,必須使用try-catch-finally方法處理。
- 執行的方法a中,先後呼叫了另外的幾個方法,這幾個方法是遞進關係執行的。我們建議這幾個方法使用throws的方式進行處理,而執行的方法a可以考慮使用try-catch-finally處理異常。
手動丟擲異常
關於異常物件的產生:
- 系統自動生成的異常物件
- 手動地生成一個異常物件,並丟擲(throw)
使用者自定義異常類
如何自定義異常類?
- 繼承與現有的異常類,RuntimeException,Exception
- 提供全域性常量:serialVersionUID,唯一標識類
- 提供過載構造器
//EcDef自定義異常類類
public class EcDef extends RuntimeException{
static final long serialVersionUID = -7034897190745766938L;
public EcDef(){
}
public EcDef(String msg){
super(msg);
}
}
//讀取兩個命令列引數,不能為負,處理各種異常。
public class exceptionExer {
public static double ecm(int i, int j){
if(i < 0 || j < 0){
throw new EcDef("輸入為負數");
}
else if(j == 0){
throw new ArithmeticException("除數為0");
}else{
return (double)i / j;
}
}
public static void main(String[] args) {
try {
if(args.length != 2){
throw new ArrayIndexOutOfBoundsException("命令列引數個數出錯");
}
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
double result = ecm(i,j);
System.out.println(result);
}catch (ArrayIndexOutOfBoundsException e){
System.out.println(e.getMessage());
}catch (NumberFormatException e){
System.out.println("數字轉換型別不匹配");
}catch (ArithmeticException e){
System.out.println(e.getMessage());
}catch (EcDef e){
System.out.println(e.getMessage());
}finally {
System.out.println("Done!");
}
}
}