Java 異常基礎詳解

1. Java 中的異常

前言:Java 中的異常處理是處理程序運行錯誤時的強大機制之一,它能夠保證應用程序的正常流程。程序員

首先咱們將瞭解java異常、異常的類型以及受查和非受查異常之間的區別。數組

1.1 什麼是異常?

字面意義:異常是一種不正常的狀況。安全

在 java 中,異常是擾亂程序正常流程的事件,它是在程序運行時拋出的對象。學習

1.2 什麼是異常處理?

異常處理一種在運行時解決程序錯誤的機制,例如 ClassNotFound、IO、SQL、Remote 等。3d

1.2.1 異常處理的優點

異常一般會干擾程序的正常流程,而異常處理的核心優點是維護程序的正常流程。如今讓咱們假設一下:指針

statement 1;  
statement 2;  
statement 3;  
statement 4;  
statement 5;//發生異常
statement 6;  
statement 7;  
statement 8;  
statement 9;  
statement 10;

假設你的程序中有10條語句,若是在第5條中出現了一個異常,那麼語句6-10將不會繼續執行。若是你使用了異常處理,那麼語句6-10的部分將正常執行,這就是咱們爲何須要在程序中使用異常處理的緣由。調試

你知道嗎?code

  • 受查和非受查異常之間的區別是什麼?
  • 代碼int data=50/0;後面發生了什麼?
  • 爲何須要使用多個catch塊?
  • finally塊是否有可能不執行?
  • 什麼是異常傳遞?
  • throwthrows關鍵字之間的區別?
  • 對方法重寫使用異常處理的4條規則是什麼?

如今讓咱們帶着以上問題繼續下面的學習。orm

1.3 Java 異常類的層次結構

throwable

1.4 異常類型

主要有兩種類型的異常:受查和非受查異常,Error被視爲非受查異常。Sun公司認爲有三種異常類型:

  • 受查異常(Checked Exception)
  • 非受查異常(UnChecked Exception)
  • 錯誤(Error)

1.5 受查和非受查異常之間的區別

1)受查異常

除了RuntimeExceptionError外,繼承自Throwable類的類稱爲受查異常,例如:IOException、SQLException 等。受查異常在編譯時進行檢查。

常見的有如下幾個方面:

  • 試圖在文件尾部後面讀取數據
  • 試圖打開一個不存在的文件
  • 試圖根據給定的字符串查找Class對象,而這個字符串表示的類並不存在

2)非受查異常

繼承自RuntimeException類的異常被稱爲非受查異常,例如:ArithmeticException、 NullPointerException、 ArrayIndexOutOfBoundsException 等。非受查異常不會在編譯時檢查,而是在運行時進行檢查。

常見的有如下幾個方面:

  • 錯誤的類型轉換
  • 數組訪問越界
  • 訪問null指針

「若是出現了RuntimeException異常,那麼必定是你自身的問題」,是一條至關有道理的規則。

3)錯誤(Error)

錯誤是一種沒法恢復的異常類型,一般是在java運行時系統的內部錯誤和資源耗盡錯誤。應用程序不該該拋出這種類型的對象。若是出現了這樣的內部錯誤,除了通告給用戶,並盡力的使得程序安全的終止以外,再也無能爲力了。這種狀況不多出現。

1.6 可能出現異常的常見場景

在某些狀況下,可能出現未檢查的異常,它們以下:

1)發生ArithmeticException的場景

若是咱們將任何數字除以0,就會出現一個 ArithmeticException 異常。

int a = 50/0;//ArithmeticException

2)發生NullPointerException的場景

若是變量的值爲null,那麼調用此變量將會出現 NullPointerException 異常。

String s = null;  
System.out.println(s.length());//NullPointerException

3)發生NumberFormatException的場景

任何值的格式錯誤,都有肯能發生 NumberFormatException 異常。假設一個字符串變量,其中包含了字符,若將此變量轉換爲數字類型,將會發生 NumberFormatException 異常。

String s = "abc";  
int i = Integer.parseInt(s);//NumberFormatException

4)發生ArrayIndexOutOfBoundsException的場景

若是你在一個不存在的的數組索引中插入任何值,則會致使 ArrayIndexOutOfBoundsException 異常。

int a[] = new int[5];  
a[10] = 50; //ArrayIndexOutOfBoundsException

1.7 Java 異常處理關鍵字

下面是 Java 異常處理中的5個關鍵字:

trycatchfinallythrowthrows

1.8 建立自定義異常類

在程序中,可能會遇到任何標準異常類都沒有可以充分地描述清楚的問題。在這種狀況下,建立本身的異常類就是一件瓜熟蒂落的事情了。咱們須要作的只是定義一個派生於 Exception 的類,或者派生於 Exception 子類的類。例如,定義一個派生於 IOException 的類。

習慣上,定義的類應該包含兩個構造器,一個是默認構造器,一個是描述詳細信息的的構造器(超類 Throwable 的 toString 方法將會打印出這些詳細信息,這在調試中很是有用。)

示例以下:

class FileFormatException extends IOException {
    public FileFormatException() {}
    public FileFormatException(String gripe) {
        super(gripe);
    }
}

如今,就能夠拋出本身定義的異常類型了。

String readData(BufferedReader in) throws FileFormatException {
    ...
    while (...) {
        // EOF encountered
        if (ch == -1) {
            if (n < len)
                throw new FileFormatException();
        }
        ...
    }
    return s;
}


2. Java try-catch

將可能發生異常的代碼放在try塊中,且必須在方法中才能使用。try 塊後必須使用catch塊或finally塊。

2.1 Java try 塊

1)try-catch 語法

try{  
// 可能拋出異常的代碼
}catch(Exception_class_Name ref){}

2)try-finally 語法

try{  
// 可能拋出異常的代碼
}finally{}

2.2 Java catch 塊

Java catch塊被用於處理異常,必須在try塊後使用。

你能夠在一個try塊後使用多個catch

2.3 未使用異常處理的問題

若是咱們不使用try-catch處理異常,看看會發生什麼。

public class Testtrycatch1 {  
    public static void main(String args[]) {  
        int data=50/0;// 可能拋出異常
        System.out.println("代碼的其他部分...");  
    }  
}

輸出:

Exception in thread main java.lang.ArithmeticException:/ by zero

如上面的示例所示,代碼的其他部分並無執行。("代碼的其他部分..."未打印)

2.4 使用異常處理解決問題

讓咱們經過try-catch塊來查看上述問題的解決方案。

public class Testtrycatch2 {  
    public static void main(String args[]) {  

        try {  
            int data = 50/0;  
        }
        catch(ArithmeticException e) {
            System.out.println(e);
        }  

        System.out.println("代碼的其他部分...");  
    }  
}

輸出:

Exception in thread main java.lang.ArithmeticException:/ by zero
代碼的其他部分...

如今,正如上面的示例所示,代碼的其他部分執行了.(也就是"代碼的其他部分..."被打印)

2.5 Java try-catch 內部工做原理

exceptionobject

Java 虛擬機首先檢查異常是否被處理,若是異常未處理,則執行的一個默認的異常處理程序:

  • 打印異常描述
  • 打印堆棧跟蹤(異常發生方法的層次結構)
  • 終止程序

若是程序員處理了異常,則應用程序按照正常流程執行。


3. 使用多個 catch 塊

若是須要在發生不一樣異常時執行不一樣的任務,則須要使用多個 catch 塊。

查看下面一個簡單的多 catch 塊示例。

public class TestMultipleCatchBlock{  
    public static void main(String args[]) {  

        try{  
            int a[] = new int[5];  
            a[5] = 30/0;  
        }  
        catch(ArithmeticException e) {
            System.out.println("任務1已完成");
        }  
        catch(ArrayIndexOutOfBoundsException e) {
            System.out.println("任務2已完成");
        }  
        catch(Exception e) {
            System.out.println("已完成通用任務");
        }

        System.out.println("代碼的其他部分...");  

    }  
}

輸出:

任務1已完成
代碼的其他部分...

規則:一次只有一個異常發生,而且一次只執行一個catch塊。

規則: 全部異常必須從最具體到最通用的順序排序,即捕獲ArithmeticException必須在捕獲Exception以前發生。

class TestMultipleCatchBlock1 {  
public static void main(String args[]) {  

        try{  
            int a[]=new int[5];  
            a[5]=30/0;  
        }
        catch(Exception e) {
            System.out.println("已完成通用任務");
        }
        catch(ArithmeticException e) {
            System.out.println("任務1已完成");
        }
        catch(ArrayIndexOutOfBoundsException e) {
            System.out.println("任務2已完成");
        }  

        System.out.println("代碼的其他部分...");  

    }  
}

輸出:

Compile-time error


4. Java 嵌套 try 塊

Java try塊中的try塊被稱爲try嵌套塊。

4.1 爲何使用 try 嵌套塊?

有時可能會出現一種狀況,一個塊的某個部分可能致使一個錯誤,而整個塊的自己可能會致使另外一個錯誤。在這種狀況下,必須使用嵌套異常處理程序。

語法:

....  
try  
{  
    statement 1;  
    statement 2;  
    try  
    {  
        statement 1;  
        statement 2;  
    }  
    catch(Exception e)  
    {  
        ...
    }  
}  
catch(Exception e) {...}  
....

4.2 Java try 嵌套塊示例

class Excep6 {
    public static void main(String args[]) {
        try {
            // try 嵌套塊1
            try {
                System.out.println("try 嵌套塊1");
                int b = 39 / 0;
            }
            catch(ArithmeticException e) {
                System.out.println(e);
            }
            // try 嵌套塊2
            try {
                int a[] = new int[5];
                a[5] = 4;
            }
            catch(ArrayIndexOutOfBoundsException e) {
                System.out.println(e);
            }
            System.out.println("try外部塊其餘語句...");
        }
        catch(Exception e) {
            System.out.println("handeled");
        }
        System.out.println("正常流...");
    }
}

輸出:

try 嵌套塊1
java.lang.ArithmeticException: / by zero
java.lang.ArrayIndexOutOfBoundsException: 5
try外部塊其餘語句...
正常流...


5. Java finally 塊

Java finally 塊是用來執行重要代碼的塊(如關閉鏈接、流等)。

不管是否處理異常,最終都會執行 finally 塊。

finally 塊緊跟 try 或 catch 塊後:

finally

注意:不管你是否處理異常,在終止程序以前,JVM都將執行finally塊(若是存在的話)

5.1 爲何要使用 finally 塊

finally 塊能夠用於放置"clear"代碼,例如關閉文件,關閉鏈接等。

5.2 使用 finally 塊案例

接下來讓咱們來看看在不一樣狀況下使用 finally 塊。

1)案例1

當前沒有發生異常:

class TestFinallyBlock {
    public static void main(String[] args) {
        try {
            int data = 25 / 5;
            System.out.println(data);
        }
        catch (NullPointerException e) {
            System.out.println(e);
        }
        finally {
            System.out.println("finally 塊老是執行");
        }
        System.out.println("代碼的其他部分...");
    }
}

輸出:

5
finally 塊老是執行
代碼的其他部分...

2)案例2

發生異常但未處理:

class TestFinallyBlock1 {
    public static void main(String[] args) {
        try {
            int data = 25 / 0;
            System.out.println(data);
        }
        catch (NullPointerException e) {
            System.out.println(e);
        }
        finally {
            System.out.println("finally 塊老是執行");
        }
        System.out.println("代碼的其他部分...");
    }
}

輸出:

finally 塊老是執行
Exception in thread main java.lang.ArithmeticException:/ by zero

3)案例3

發生異常並處理異常:

public class TestFinallyBlock2 {
    public static void main(String args[]) {
        try {
            int data = 25 / 0;
            System.out.println(data);
        }
        catch(ArithmeticException e) {
            System.out.println(e);
        }
        finally {
            System.out.println("finally 塊老是執行");
        }
        System.out.println("代碼的其他部分...");
    }
}

輸出:

Exception in thread main java.lang.ArithmeticException:/ by zero
finally 塊老是執行
代碼的其他部分...

規則:對於 try 塊能夠有0個或多個 catch 塊,但僅僅只能有一個 finally 塊。

規則:若是程序退出(經過調用 System.exit() 或經過致使進程停止的致命錯誤),finally塊將不會被執行。


6. Java 拋出異常

6.1 Java throw 關鍵字

Java throw 關鍵字用於顯示的拋出異常。

咱們可使用 throw 關鍵字在 Java 中拋出檢查(Checked)或未檢查(UnChecked)異常。throw 關鍵字主要用於拋出自定義異常。

Java throw 語法以下:

throw exception;

拋出IOException異常的例子:

throw new IOException("sorry device error");

6.2 Java throw 示例

在本例中,咱們建立了一個將整數值做爲參數的 validate 方法。若是年齡小於18歲,咱們將拋出一個ArithmeticException異常,不然打印一條消息"歡迎投票"。

public class TestThrow1 {
    static void validate(int age) {
        if(age < 18)  
            throw new ArithmeticException("無效");
        else  
            System.out.println("歡迎投票");
    }

    public static void main(String args[]) {
        validate(13);
        System.out.println("代碼的其他部分...");
    }
}

輸出:

Exception in thread main java.lang.ArithmeticException:無效


7. Java 異常傳遞

異常首先從堆棧頂部拋出,若是未捕獲,則將調用堆棧降低到前一個方法,若是沒有捕獲,則將異常再次降低到先前的方法,以此類推,知道它們被捕獲或到達調用堆棧底部爲止。以上稱爲異常傳遞。

規則:默認狀況下,非受查異常在調用鏈中(傳遞)轉發。

異常傳遞示例:

class TestExceptionPropagation1 {
    void m(){
        int data = 50 / 0;
    }
    void n() {
        m();
    }
    void p() {
        try{
            n();
        }
        catch(Exception e) {
            System.out.println("異常處理器");
        }
    }

    public static void main(String args[]) {
        TestExceptionPropagation1 obj = new TestExceptionPropagation1();
        obj.p();
        System.out.println("正常流...");
    }
}

輸出:

異常處理器
正常流...

propagation

在上面的示例中。異常發生在 m() 方法中,若是未對其進行處理,則將其傳遞到未處理它的前 n() 方法,再次將其傳遞處處理異常的 p() 方法。

能夠在 main()、p()、n()、p()、 m() 中的任何方法中處理異常。

規則:默認狀況下,受查異常不會在調用鏈中(傳遞)轉發。

用於描述受查異常不會在程序中傳遞的示例:

class TestExceptionPropagation2{
    void m(){
        throw new java.io.IOException("設備異常"); // 受查異常
    }
    void n(){
        m();
    }
    void p(){
        try{
            n();
        }
        catch(Exception e){
            System.out.println("異常處理器");
        }
    }
    public static void main(String args[]){
        TestExceptionPropagation2 obj=new TestExceptionPropagation2();
        obj.p();
        System.out.println("正常流...");
    }
}

輸出:

Compile Time Error

編譯時發生一個錯誤,證實受查異常並不會在程序中進行傳遞。


8. Java throws 關鍵字

Java throws 關鍵字被用於聲明一個異常。它給程序員提供了一個信息,說明可能會發生異常,因此程序員最好提供異常處理代碼,以保證程序正常的流程。

異常處理主要用於處理受查異常,若是出現任何非受查異常,如"NullPointerException",都是程序員自身的錯誤,請認真檢查你的代碼。

8.1 Java throws 語法

return_type method_name() throws exception_class_name {  
    // method code  
}

8.2 應該聲明哪一個異常?

僅僅聲明受查異常,由於:

  • 非受查異常:程序員應該更正代碼以確保代碼正確無誤。
  • Error:沒法控制,若是出現了 VirtualMachineErrorStackOverflowError等異常,將沒法進行任何操做。

8.3 Java throws 優點

使用 throws 聲明受查異常後,使得受查異常能夠在調用堆棧中進行(傳遞)轉發。它向處理該異常的方法提供異常信息。

8.4 Java throws 示例

下面的示例描述了受查異常能夠經過throws關鍵字進行傳遞:

import java.io.IOException;
class Testthrows1{
    void m() throws IOException{
        throw new IOException("設備異常"); // 受查異常
    }
    void n()throws IOException{
        m();
    }
    void p(){
        try{
            n();
        }
        catch(Exception e){
            System.out.println("異常處理器");
        }
    }
    public static void main(String args[]){
        Testthrows1 obj=new Testthrows1();
        obj.p();
        System.out.println("正常流...");
    }
}

輸出:

異常處理器
正常流...

規則:若是你正在調用一個聲明瞭異常的方法,則必須捕獲或聲明異常。

如今有兩種狀況:

  • 狀況1:你遇到了一個異常,使用 try-catch 處理了異常。
  • 狀況2:你聲明瞭異常,使用方法指定拋出。

1) 狀況1:處理了異常

  • 在這種狀況下,若是你處理了異常,則無論程序是否出現了異常,程序都將繼續執行。
import java.io.*;
class M{
    void method() throws IOException{
        throw new IOException("設備異常");
    }
}
public class Testthrows2{
    public static void main(String args[]){
        try{
            M m = new M();
            m.method();
        }
        catch(Exception e){
            System.out.println("異常處理器");
        }
        System.out.println("正常流...");
    }
}

輸出:

異常處理器
正常流...

2) 狀況2:聲明瞭異常

  • A)若是聲明瞭異常,但代碼未出現異常,程序將正常執行。
  • B)若是聲明瞭異常且發生了異常,則在運行時拋出異常,由於程序會拋出不處理的異常。

A)聲明瞭異常但未發生異常:

import java.io.*;
class M{
    void method()throws IOException{
        System.out.println("執行設備操做");
    }
}
class Testthrows3{
    public static void main(String args[])throws IOException{
        // 聲明瞭異常
        M m=new M();
        m.method();
        System.out.println("正常流...");
    }
}

輸出:

執行設備操做
正常流...

B)聲明瞭異常且發生了異常:

import java.io.*;
class M{
    void method()throws IOException{
        throw new IOException("設備錯誤");
    }
}
class Testthrows4{
    public static void main(String args[])throws IOException{
        // 聲明瞭異常  
        M m=new M();
        m.method();
        System.out.println("正常流...");
    }
}

輸出:

Runtime Exception

程序編譯時將直接出現了一個編譯錯誤。

8.5 throw 與 throws 區別

No. throw throws
1) Java throw 關鍵字用於顯示的拋出異常 Java throws 關鍵字用於聲明一個異常
2) 受查異常不能只使用 throw 進行傳遞 受查異常能夠經過 throws 進行傳遞
3) Throw 後面跟着一個異常實例 Throws 後面跟着一個異常類
4) 在方法中使用 Throw Throws 與方法簽名一塊兒使用
5) 你不能拋出多個異常 你能夠聲明多個異常,例如public void method() throws IOException,SQLException

1)Java throw 示例:

void m(){  
    throw new ArithmeticException("sorry");  
}

2)Java throws 示例:

void m()throws ArithmeticException{  
    // method code  
}

3)Java throw 和 throws 示例:

void m()throws ArithmeticException{  
    throw new ArithmeticException("sorry");  
}

8.6 思考:能夠從新拋出一個異常嗎?

答案固然是能夠的,能夠在 catch 塊中拋出相同的異常。這種方法一般用於只想記錄一個異常,但不作任何改變。

代碼示例:

try {
    // access the database
}
catch (Exceptiom e) {
    logger.log(level, message, e);
    throw e;
}

在 Java SE7 以前,這種方法存在一個問題,假設這段代碼在如下方法中:

public void updateRecord() throws SQLException

Java 編譯器查看 catch 塊中的 throw 語句,而後查看 e 的類型,會指出這個方法能夠拋出任何 Exception 而不只僅是 SQLException。如今這個問題已經有所改進,編譯器會追蹤到 e 來自 try 塊。假設這個 try 塊僅有的受查異常是 SQLException 實例,另外,假設 e 在 catch 塊中未改變,將外圍方法聲明爲 throws SQLException 是合法的。


9. Final 和 Finally 和 Finalize 對比

Final 和 Finally 和 Finalize 三者之間的差別以下:

No. final finally finalize
1) final 用於對類、方法和變量加以限制,final 類不能被繼承,final 方法不能被重寫,final 變量不能被更改 finally 用於放置重要的代碼,不管異常是否被處理它都會執行 finalize 用於在對象被垃圾回收以前執行清理操做
2) final 是一個關鍵字 finally 是一個塊 finalize 是一個方法

1)Java final 示例:

class FinalExample{
    public static void main(String[] args){
        final int x = 100;
        x = 200; // final 修飾的變量不能被更改
        // 編譯時將出錯
    }
}

2)Java finally 示例:

class FinallyExample{
    public static void main(String[] args){
        try{
            int x = 300;
        }
        catch(Exception e){
            System.out.println(e);
        }
        finally{
            System.out.println("finally 塊始終被執行");
        }
    }
}

3)Java finalize 示例:

class FinalizeExample{
    public void finalize(){
        System.out.println("finalize called");
    }
    public static void main(String[] args){
        FinalizeExample f1 = new FinalizeExample();
        FinalizeExample f2 = new FinalizeExample();
        f1 = null;
        f2 = null;
        System.gc();
    }
}

10. 異常處理方法的重寫

關於重寫異常處理方法的規則以下:

  • 超類方法沒有聲明異常:
    若是超類方法沒有聲明異常,則子類重寫方法不能聲明受查異常,但能夠聲明非受查異常。
  • 超類方法聲明瞭異常:
    若是超類方法聲明瞭異常,則子類重寫方法能夠聲明與超類方法相同的異常,也能夠不聲明異常。若父類方法聲明父類異常,子類重寫方法聲明子類異常也能夠,反之不能夠。

1)若是超類方法沒有聲明異常

超類方法未聲明異常,子類重寫方法聲明受查異常的示例:

import java.io.*;
class Parent{
    void msg(){
        System.out.println("parent");
    }
}
class TestExceptionChild extends Parent{
    void msg() throws IOException{
        System.out.println("Child");
    }
    public static void main(String args[]){
        Parent p = new TestExceptionChild();
        p.msg();
    }
}

輸出:

Compile Time Error

超類方法未聲明異常,子類重寫方法聲明非受查異常的示例:

import java.io.*;
class Parent{
    void msg(){
        System.out.println("parent");
    }
}
class TestExceptionChild1 extends Parent{
    void msg() throws ArithmeticException{
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p = new TestExceptionChild1();
        p.msg();
    }
}

輸出:

child

2)若是超類方法聲明瞭異常

A)超類方法聲明瞭異常,子類重寫方法聲明不相同父類異常的示例:

import java.io.*;
class Parent{
    // 聲明瞭子類異常
    void msg() throws ArithmeticException{
        System.out.println("parent");
    }
}
class TestExceptionChild2 extends Parent{
    // 聲明瞭父類異常
    void msg() throws Exception{
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p = new TestExceptionChild2();
        try{
            p.msg();
        }
        catch(Exception e){
        }
    }
}

輸出:

Compile Time Error

B)超類方法聲明瞭異常,子類重寫方法聲明相同異常的示例:

import java.io.*;
class Parent{
    void msg()throws Exception{
        System.out.println("parent");
    }
}
class TestExceptionChild3 extends Parent{
    void msg()throws Exception{
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p=new TestExceptionChild3();
        try{
            p.msg();
        }
        catch(Exception e){
        }
    }
}

輸出:

child

C)超類方法聲明瞭異常,子類重寫方法聲明不相同子類異常的示例:

import java.io.*;
class Parent{
    // 聲明瞭父類異常
    void msg()throws Exception{
        System.out.println("parent");
    }
}
class TestExceptionChild4 extends Parent{
    // 聲明瞭子類異常
    void msg()throws ArithmeticException{
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p=new TestExceptionChild4();
        try{
            p.msg();
        }
        catch(Exception e){
        }
    }
}

輸出:

child

D)超類方法聲明瞭異常,子類重寫方法未聲明異常的示例:

import java.io.*;
class Parent{
    void msg()throws Exception{
        System.out.println("parent");
    }
}
class TestExceptionChild5 extends Parent{
    void msg(){
        System.out.println("child");
    }
    public static void main(String args[]){
        Parent p=new TestExceptionChild5();
        try{
            p.msg();
        }
        catch(Exception e){
        }
    }
}

輸出:

child

參考文章:https://www.javatpoint.com/exception-handling-in-java 參考書籍:《Java核心技術 卷1》

相關文章
相關標籤/搜索