1. 程式人生 > >Java程式設計師必備:異常的十個關鍵知識點

Java程式設計師必備:異常的十個關鍵知識點

前言

總結了Java異常十個關鍵知識點,面試或者工作中都有用哦,加油。

一. 異常是什麼

異常是指阻止當前方法或作用域繼續執行的問題。比如你讀取的檔案不存在,陣列越界,進行除法時,除數為0等都會導致異常。

一個檔案找不到的異常:

public class TestException {
    public static void main(String[] args) throws IOException {
        InputStream is = new FileInputStream("jaywei.txt");
        int b;
        while ((b = is.read()) != -1) {

        }
    }
}

執行結果:

Exception in thread "main" java.io.FileNotFoundException: jaywei.txt (系統找不到指定的檔案。)
    at java.io.FileInputStream.open0(Native Method)
    at java.io.FileInputStream.open(FileInputStream.java:195)
    at java.io.FileInputStream.<init>(FileInputStream.java:138)
    at java.io.FileInputStream.<init>(FileInputStream.java:93)
    at exception.TestException.main(TestException.java:10)

二. 異常的層次結構

從前從前,有位老人,他的名字叫Throwable,他生了兩個兒子,大兒子叫Error,二兒子叫Exception。

Error

表示編譯時或者系統錯誤,如虛擬機器相關的錯誤,OutOfMemoryError等,error是無法處理的。

Exception

程式碼異常,Java程式設計師關心的基型別通常是Exception。它能被程式本身可以處理,這也是它跟Error的區別。

它可以分為RuntimeException(執行時異常)和CheckedException(可檢查的異常)。

常見的RuntimeException異常:

- NullPointerException 空指標異常
- ArithmeticException 出現異常的運算條件時,丟擲此異常
- IndexOutOfBoundsException 陣列索引越界異常
- ClassNotFoundException 找不到類異常
- IllegalArgumentException(非法引數異常)

常見的 Checked Exception 異常:

- IOException (操作輸入流和輸出流時可能出現的異常)
- ClassCastException(型別轉換異常類)
  • Checked Exception就是編譯器要求你必須處置的異常。
  • 與之相反的是,Unchecked Exceptions,它指編譯器不要求強制處置的異常,它包括Error和RuntimeException 以及他們的子類。

三、異常處理

當異常出現後,會在堆上建立異常物件。當前的執行路徑被終止,並且從當前環境中彈出對異常物件的引用。這時候異常處理程式,使程式從錯誤狀態恢復,使程式繼續執行下去。

異常處理主要有丟擲異常、捕獲異常、宣告異常。如圖:

捕獲異常

try{
// 程式程式碼
}catch(Exception e){
//Catch 塊
}finaly{
  //無論如何,都會執行的程式碼塊 
}

我們可以通過try...catch...捕獲異常程式碼,再通過finaly執行最後的操作,如關閉流等操作。

宣告丟擲異常

除了try...catch...捕獲異常,我們還可以通過throws宣告丟擲異常。

當你定義了一個方法時,可以用throws關鍵字宣告。使用了throws關鍵字表明,該方法不處理異常,而是把異常留給它的呼叫者處理。是不是覺得TA不負責任?

哈哈,看一下demo吧

//該方法通過throws聲明瞭IO異常。
 private void readFile() throws IOException {
        InputStream is = new FileInputStream("jaywei.txt");
        int b;
        while ((b = is.read()) != -1) {

        }
    }

從方法中宣告丟擲的任何異常都必須使用throws子句。

丟擲異常

throw關鍵字作用是丟擲一個Throwable型別的異常,它一般出現在函式體中。在異常處理中,try語句要捕獲的是一個異常物件,其實此異常物件也可以自己丟擲。

例如丟擲一個 RuntimeException 類的異常物件:

throw new RuntimeException(e);

任何Java程式碼都可以通過 Java 的throw語句丟擲異常。

注意點

  • 非檢查異常(Error、RuntimeException 或它們的子類)不可使用 throws 關鍵字來宣告要丟擲的異常。
  • 一個方法出現編譯時異常,就需要 try-catch/ throws 處理,否則會導致編譯錯誤。

四、try-catch-finally-return執行順序

try-catch-finally-return 執行描述

  • 如果不發生異常,不會執行catch部分。
  • 不管有沒有發生異常,finally都會執行到。
  • 即使try和catch中有return時,finally仍然會執行
  • finally是在return後面的表示式運算完後再執行的。(此時並沒有返回運算後的值,而是先把要返回的值儲存起來,若finally中無return,則不管finally中的程式碼怎麼樣,返回的值都不會改變,仍然是之前儲存的值),該情況下函式返回值是在finally執行前確定的)
  • finally部分就不要return了,要不然,就回不去try或者catch的return了。

看一個例子

 public static void main(String[] args) throws IOException {
        System.out.println("result:" + test());
    }

    private static int test() {
        int temp = 1;
        try {
            System.out.println("start execute try,temp is:"+temp);
            return ++temp;
        } catch (Exception e) {
            System.out.println("start execute catch temp is: "+temp);
            return ++temp;
        } finally {
            System.out.println("start execute finally,temp is:" + temp);
            ++temp;
        }
    }

執行結果:

start execute try,temp is:1
start execute finally,temp is:2
result:2

分析

  • 先執行try部分,輸出日誌,執行++temp表示式,temp變為2,這個值被儲存起來。
  • 因為沒有發生異常,所以catch程式碼塊跳過。
  • 執行finally程式碼塊,輸出日誌,執行++temp表示式.
  • 返回try部分儲存的值2.

五、Java異常類的幾個重要方法

先來喵一眼異常類的所有方法,如下圖:

getMessage

Returns the detail message string of this throwable.

getMessage會返回Throwable的detailMessage屬性,而detailMessage就表示發生異常的詳細訊息描述。

舉個例子,FileNotFoundException異常發生時,這個detailMessage就包含這個找不到檔案的名字。

getLocalizedMessage

Creates a localized description of this throwable.Subclasses may override this
method in order to produce alocale-specific message. For subclasses that do not
override thismethod, the default implementation returns the same result
as getMessage()

throwable的本地化描述。子類可以重寫此方法,以生成特定於語言環境的訊息。對於不覆蓋此方法的子類,預設實現返回與相同的結果 getMessage()。

getCause

Returns the cause of this throwable or null if thecause is nonexistent or unknown.

返回此可丟擲事件的原因,或者,如果原因不存在或未知,返回null。

printStackTrace

Prints this throwable and its backtrace to thestandard error stream.

The first line of output contains the result of the toString() method for
this object.Remaining lines represent data previously recorded by the 
method fillInStackTrace(). 

該方法將堆疊跟蹤資訊列印到標準錯誤流。

輸出的第一行,包含此物件toString()方法的結果。剩餘的行表示,先前被方法fillInStackTrace()記錄的資料。如下例子:

 java.lang.NullPointerException
         at MyClass.mash(MyClass.java:9)
         at MyClass.crunch(MyClass.java:6)
         at MyClass.main(MyClass.java:3)

六、自定義異常

自定義異常通常是定義一個繼承自 Exception 類的子類。

那麼,為什麼需要自定義異常?

  • Java提供的異常體系不可能預見所有的錯誤。
  • 業務開發中,使用自定義異常,可以讓專案程式碼更加規範,也便於管理。

下面是我司自定義異常類的一個簡單demo

public class BizException extends Exception {
    //錯誤資訊
    private String message;
    //錯誤碼
    private String errorCode;

    public BizException() {
    }

    public BizException(String message, String errorCode) {
        this.message = message;
        this.errorCode = errorCode;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }
}

跑個main方測試一下


public class TestBizException {

    public static void testBizException() throws BizException {
        System.out.println("throwing BizException from testBizException()");
        throw new BizException("100","哥,我錯了");
    }

    public static void main(String[] args) {
        try {
            testBizException();
        } catch (BizException e) {
            System.out.println("自己定義的異常");
            e.printStackTrace();
        }
    }
}

執行結果:

exception.BizException: 100
throwing BizException from testBizException()
自己定義的異常
    at exception.TestBizException.testBizException(TestBizException.java:7)
    at exception.TestBizException.main(TestBizException.java:12)

七、Java7 新的 try-with-resources語句

try-with-resources,是Java7提供的一個新功能,它用於自動資源管理。

  • 資源是指在程式用完了之後必須要關閉的物件。
  • try-with-resources保證了每個聲明瞭的資源在語句結束的時候會被關閉
  • 什麼樣的物件才能當做資源使用呢?只要實現了java.lang.AutoCloseable介面或者java.io.Closeable介面的物件,都OK。

try-with-resources出現之前

try{
    //open resources like File, Database connection, Sockets etc
} catch (FileNotFoundException e) {
    // Exception handling like FileNotFoundException, IOException etc
}finally{
    // close resources
}

Java7,try-with-resources出現之後,使用資源實現

try(// open resources here){
    // use resources
} catch (FileNotFoundException e) {
    // exception handling
}
// resources are closed as soon as try-catch block is executed.

Java7使用資源demo

public class Java7TryResourceTest {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader(
                "C:/jaywei.txt"))) {
            System.out.println(br.readLine());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用了try-with-resources的好處

  • 程式碼更加優雅,行數更少。
  • 資源自動管理,不用擔心記憶體洩漏問題。

八、異常鏈

我們常常會想要在捕獲一個異常後丟擲另一個異常,並且希望把原始異常的資訊儲存下來,這被稱為異常鏈。

throw 丟擲的是一個新的異常資訊,這樣會導致原有的異常資訊丟失。在JDk1.4以前,程式設計師必須自己編寫程式碼來儲存原始異常資訊。現在所有Throwable子類在構造器中都可以接受一個cause(異常因由)物件作為引數。

這個cause就用來表示原始異常,這樣通過把原始異常傳遞給新的異常,使得即使當前位置建立並丟擲了新的異常,也能通過這個異常鏈追蹤到異常最初發生的位置。

使用方式如下:

public class TestChainException {

    public void readFile() throws MyException{
        try {
            InputStream is = new FileInputStream("jay.txt");
            Scanner in = new Scanner(is);
            while (in.hasNext()) {
                System.out.println(in.next());
            }
        } catch (FileNotFoundException e) {
            //e 儲存異常資訊
            throw new MyException("檔案在哪裡呢", e);
        }
    }

    public void invokeReadFile() throws MyException{
        try {
            readFile();
        } catch (MyException e) {
            //e 儲存異常資訊
            throw new MyException("檔案找不到", e);
        }
    }

    public static void main(String[] args) {
        TestChainException t = new TestChainException();
        try {
            t.invokeReadFile();
        } catch (MyException e) {
            e.printStackTrace();
        }
    }

}

//MyException 構造器
public MyException(String message, Throwable cause) {
        super(message, cause);
    }

執行結果:

我們可以看到異常資訊有儲存下來的,如果把cause(也就是FileNotFoundException 的e)去掉呢,看一下執行結果:

可以發現,少了Throwable cause,原始異常資訊不翼而飛了。

九、異常匹配

丟擲異常的時候,異常處理系統會按照程式碼的書寫順序找出"最近"的處理程式。 找到匹配的處理程式之後,它就認為異常將得到處理,然後就不再繼續查詢。

查詢的時候並不要求丟擲的異常同處理程式的異常完全匹配。派生類的物件也可以配備其基類的處理程式

看demo

package exceptions;
//: exceptions/Human.java
// Catching exception hierarchies.

class Annoyance extends Exception {}
class Sneeze extends Annoyance {}

public class Human {
  public static void main(String[] args) {
    // Catch the exact type:
    try {
      throw new Sneeze();
    } catch(Sneeze s) {
      System.out.println("Caught Sneeze");
    } catch(Annoyance a) {
      System.out.println("Caught Annoyance");
    }
    // Catch the base type:
    try {
      throw new Sneeze();
    } catch(Annoyance a) {
      System.out.println("Caught Annoyance");
    }
  }
}

執行結果:

catch(Annoyance a)會捕獲Annoyance以及所有從它派生的異常。
捕獲基類的異常,就可以匹配所有派生類的異常

try {
      throw new Sneeze();
    } catch(Annoyance a) {
    } catch(Sneeze s) { //這句編譯器會報錯,因為異常已由前面catch子句處理
    }

十、Java常見異常

NullPointerException

空指標異常,最常見的一個異常類。簡言之,呼叫了未經初始化的物件或者是不存在的物件,就會產生該異常。

ArithmeticException

算術異常類,程式中出現了除數為0這樣的運算,就會出現這樣的異常。

ClassCastException

型別強制轉換異常,它是JVM在檢測到兩個型別間轉換不相容時引發的執行時異常。

ArrayIndexOutOfBoundsException

陣列下標越界異常,跟陣列打交道時,需要注意一下這個異常。

FileNotFoundException

檔案未找到異常,一般是要讀或者寫的檔案,找不到,導致該異常。

SQLException

操作資料庫異常,它是Checked Exception(檢查異常);

IOException

IO異常,一般跟讀寫檔案息息相關,它也是Checked Exception(檢查異常)。平時讀寫檔案,記得IO流關閉!

NoSuchMethodException

方法未找到異常

NumberFormatException

字串轉換為數字異常

總結

這個總結獨闢蹊徑,以幾道經典異常面試題結束吧,以幫助大家複習一下,嘻嘻。

  • java 異常有哪幾種,特點是什麼?(知識點二可答)
  • 什麼是Java中的異常?(知識點一可答)
  • error和exception有什麼區別?(知識點二可答)
  • 什麼是異常鏈?(知識點八可答)
  • try-catch-finally-return執行順序(知識點四可答)
  • 列出常見的幾種RunException (知識點二可答)
  • Java異常類的重要方法是什麼?(知識點五可答)
  • error和exception的區別,CheckedException,RuntimeException的區別。(知識點二可答)
  • 請列出5個執行時異常。(知識點二可答)
  • Java 7 新的 try-with-resources 語句(知識點七可答)
  • 怎麼自定義異常?(知識點六可答)
  • 說一下常見異常以及產生原因(知識點十可答)
  • 談談異常匹配(知識點九可答)
  • 談談異常處理(知識點三可答)

個人公眾號

  • 如果你是個愛學習的好孩子,可以關注我公眾號,一起學習討論。
  • 如果你覺得本文有哪些不正確的地方,可以評論,也可以關注我公眾號,私聊我,大家一起學習進步哈。