深度分析:Java中如何如理異常,一篇幫你搞定!

異常的背景

初識異常

咱們曾經的代碼中已經接觸了一些 「異常」 了. 例如:java

除以 0編程

System.out.println(10 / 0);
// 執行結果
Exception in thread "main" java.lang.ArithmeticException: / by zero

數組下標越界數組

int[] arr = {1, 2, 3};
System.out.println(arr[100]);
// 執行結果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100

訪問 null 對象ide

public class Test {
    public int num = 10;
    public static void main(String[] args) {
        Test t = null;
        System.out.println(t.num);
   }
}
// 執行結果
Exception in thread "main"java.lang.NullPointerException

所謂異常指的就是程序在 運行時 出現錯誤時通知調用者的一種機制.指針

防護式編程

錯誤在代碼中是客觀存在的. 所以咱們要讓程序出現問題的時候及時通知程序猿. 咱們有兩種主要的方式code

LBYL: Look Before You Leap. 在操做以前就作充分的檢查.
EAFP: It’s Easier to Ask Forgiveness than Permission. 先操做, 遇到問題再處理.對象

異常的好處

例如, 咱們用僞代碼演示一下開始一局王者榮耀的過程.遊戲

LBYL 風格的代碼(不使用異常)資源

boolean ret = false;
ret = 登錄遊戲();
if (!ret) {
 處理登錄遊戲錯誤;
    return;
}
ret = 開始匹配();
if (!ret) {
 處理匹配錯誤;
    return;
}
ret = 遊戲確認();
if (!ret) {
 處理遊戲確認錯誤;
    return;
}
ret = 選擇英雄();
if (!ret) {
    處理選擇英雄錯誤;
    return;
}
ret = 載入遊戲畫面();
if (!ret) {
 處理載入遊戲錯誤;
    return;
}

EAFP 風格的代碼(使用異常)編譯器

try {
    登錄遊戲();
    開始匹配();
    遊戲確認();
    選擇英雄();
    載入遊戲畫面();
   ...
} catch (登錄遊戲異常) {
    處理登錄遊戲異常;
} catch (開始匹配異常) {
 處理開始匹配異常;
} catch (遊戲確認異常) {
 處理遊戲確認異常;
} catch (選擇英雄異常) {
 處理選擇英雄異常;
} catch (載入遊戲畫面異常) {
 處理載入遊戲畫面異常;
}

對比兩種不一樣風格的代碼, 咱們能夠發現, 使用第一種方式, 正常流程和錯誤處理流程代碼混在一塊兒, 代碼總體顯的比較混亂. 而第二種方式正常流程和錯誤流程是分離開的, 更容易理解代碼

異常的基本用法

捕獲異常
基本語法

try{
 有可能出現異常的語句 ;
}[catch (異常類型 異常對象) {
} ... ]
[finally {
 異常的出口
}]

1.try 代碼塊中放的是可能出現異常的代碼.
2.catch 代碼塊中放的是出現異常後的處理行爲.
3.finally 代碼塊中的代碼用於處理善後工做, 會在最後執行.
4.其中 catch 和 finally 均可以根據狀況選擇加或者不加.
代碼示例1 不處理異常

int[] arr = {1, 2, 3};
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
// 執行結果
before
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100

咱們發現一旦出現異常, 程序就終止了. after 沒有正確輸出.

代碼示例2 使用 try catch 後的程序執行過程

int[] arr = {1, 2, 3};
try {
    System.out.println("before");
    System.out.println(arr[100]);
    System.out.println("after");
} catch (ArrayIndexOutOfBoundsException e) {
    // 打印出現異常的調用棧
    e.printStackTrace();
}
System.out.println("after try catch");
// 執行結果
before
java.lang.ArrayIndexOutOfBoundsException: 100
 at demo02.Test.main(Test.java:10)
after try catch

咱們發現, 一旦 try 中出現異常, 那麼 try 代碼塊中的程序就不會繼續執行, 而是交給 catch 中的代碼來執行. catch 執行完畢會繼續往下執行.

代碼示例3 catch 只能處理對應種類的異常
咱們修改了代碼, 讓代碼拋出的是空指針異常.

int[] arr = {1, 2, 3};
try {
    System.out.println("before");
    arr = null;
    System.out.println(arr[100]);
    System.out.println("after");
} catch (ArrayIndexOutOfBoundsException e) {
    e.printStackTrace();
}
System.out.println("after try catch");
// 執行結果
before
Exception in thread "main" java.lang.NullPointerException
 at demo02.Test.main(Test.java:11)

此時, catch 語句不能捕獲到剛纔的空指針異常. 由於異常類型不匹配.

代碼示例4 catch 能夠有多個

int[] arr = {1, 2, 3};
try {
    System.out.println("before");
    arr = null;
    System.out.println(arr[100]);
    System.out.println("after");
} catch (ArrayIndexOutOfBoundsExceptione) {
 System.out.println("這是個數組下標越界異常");
    e.printStackTrace();
} catch (NullPointerException e) {
 System.out.println("這是個空指針異常");
    e.printStackTrace();
}
System.out.println("after try catch");
// 執行結果
before
這是個空指針異常
java.lang.NullPointerException
 at demo02.Test.main(Test.java:12)
after try catch

一段代碼可能會拋出多種不一樣的異常, 不一樣的異常有不一樣的處理方式. 所以能夠搭配多個 catch 代碼塊.若是多個異常的處理方式是徹底相同, 也能夠寫成這樣

catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
 ...
}

代碼示例5 也能夠用一個 catch 捕獲全部異常(不推薦)

int[] arr = {1, 2, 3};
try {
    System.out.println("before");
    arr = null;
    System.out.println(arr[100]);
    System.out.println("after");
} catch (Exception e) {
    e.printStackTrace();
}
System.out.println("after try catch");
// 執行結果
before
java.lang.NullPointerException
 at demo02.Test.main(Test.java:12)
after try catch

代碼示例6 finally 表示最後的善後工做, 例如釋放資源

int[] arr = {1, 2, 3};
try {
    System.out.println("before");
    arr = null;
    System.out.println(arr[100]);
    System.out.println("after");
} catch (Exception e) {
    e.printStackTrace();
} finally {
    System.out.println("finally code");
}
// 執行結果
before
java.lang.NullPointerException
 at demo02.Test.main(Test.java:12)
finally code

不管是否存在異常, finally 中的代碼必定都會執行到. 保證最終必定會執行到 Scanner 的 close 方法

代碼示例7 使用 try 負責回收資源
剛纔的代碼能夠有一種等價寫法, 將 Scanner 對象在 try 的 ( ) 中建立, 就能保證在 try 執行完畢後自動調用 Scanner的 close 方法.

try (Scanner sc = new Scanner(System.in)) {
    int num = sc.nextInt();
    System.out.println("num = " + num);
} catch (Exception e) {
    e.printStackTrace();
}

代碼示例8 若是本方法中沒有合適的處理異常的方式, 就會沿着調用棧向上傳遞

public static void main(String[] args) {
    try {
        func();
   } catch (ArrayIndexOutOfBoundsException e) {
        e.printStackTrace();
   }
    System.out.println("after try catch");
}
public static void func() {
    int[] arr = {1, 2, 3};
    System.out.println(arr[100]);
}
// 直接結果
java.lang.ArrayIndexOutOfBoundsException: 100
 at demo02.Test.func(Test.java:18)
 at demo02.Test.main(Test.java:9)
after try catch

代碼示例9 若是向上一直傳遞都沒有合適的方法處理異常, 最終就會交給 JVM 處理, 程序就會異常終止(和咱們最開始未使用 try catch 時是同樣的)

public static void main(String[] args) {
    func();
 System.out.println("after try catch");
}
public static void func() {
    int[] arr = {1, 2, 3};
    System.out.println(arr[100]);
}
// 執行結果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
 at demo02.Test.func(Test.java:14)
 at demo02.Test.main(Test.java:8)

能夠看到, 程序已經異常終止了, 沒有執行到System.out.println(「after try catch」); 這一行

異常處理流程

程序先執行 try 中的代碼
若是 try 中的代碼出現異常, 就會結束 try 中的代碼, 看和 catch 中的異 常類型是否匹配.
若是找到匹配的異常類型, 就會執行 catch 中的代碼
若是沒有找到匹配的異常類型, 就會將異常向上傳遞到上層調用者.
不管是否找到匹配的異常類型, finally 中的代碼都會被執行到(在該方法結束以前執行).
若是上層調用者也沒有處理的了異常, 就繼續向上傳遞.
一直到 main 方法也沒有合適的代碼處理異常, 就會交給 JVM 來進行處理, 此時程序就會異常終止.

拋出異常

除了 Java 內置的類會拋出一些異常以外, 程序猿也能夠手動拋出某個異常. 使用 throw 關鍵字完成這個操做.

public static void main(String[] args) {
 System.out.println(divide(10, 0));
}
public static int divide(int x, int y) {
 if (y == 0) {
 throw new ArithmeticException("拋出除 0 異常");
 }
 return x / y;
} 
// 執行結果
Exception in thread "main" java.lang.ArithmeticException: 拋出除 0 異常
 at demo02.Test.divide(Test.java:14)
 at demo02.Test.main(Test.java:9)

在這個代碼中, 咱們能夠根據實際狀況來拋出須要的異常. 在構造異常對象同時能夠指定一些描述性信息

異常說明

咱們在處理異常的時候, 一般但願知道這段代碼中究竟會出現哪些可能的異常.
咱們可使用 throws 關鍵字, 把可能拋出的異常顯式的標註在方法定義的位置. 從而提醒調用者要注意捕獲這些異常

public static int divide(int x, int y) throws ArithmeticException {
 if (y == 0) {
 throw new ArithmeticException("拋出除 0 異常");
 }
 return x / y;
}

關於 finally 的注意事項

finally 中的代碼保證必定會執行到. 這也會帶來一些麻煩

public static void main(String[] args) {
 System.out.println(func());
}
public static int func() {
 try {
 return 10;
 } finally {
 return 20;
 }
}
// 執行結果
20

注意:
finally 執行的時機是在方法返回以前(try 或者 catch 中若是有 return 會在這個 return 以前執行 finally). 可是若是finally 中也存在 return 語句, 那麼就會執行 finally 中的 return, 從而不會執行到 try 中原有的 return.通常咱們不建議在 finally 中寫 return (被編譯器當作一個警告).

自定義異常類

Java 中雖然已經內置了豐富的異常類, 可是咱們實際場景中可能還有一些狀況須要咱們對異常類進行擴展, 建立符合咱們實際狀況的異常.
例如, 實現一個用戶登錄功能

class UserException extends Exception {
    public UserException(String message) {
        super(message);
    }
}

class PasswordException extends Exception {
    public PasswordException(String message) {
        super(message);
    }
}

public class TestDemo3 {
    private static String userName = "admin";
    private static String password = "123456";

    public static void main(String[] args) {
        try {
        login("admin", "123456");
        } catch (UserException userError) {
        userError.printStackTrace();
        } catch (PasswordException  passwordError) {
        passwordError.printStackTrace();
        }
    }

    public static void login(String userName, String password) throws UserException, PasswordException {
        if (!TestDemo3.userName.equals(userName)) {
        throw new UserException("用戶名錯誤");
        }
        if (!TestDemo3.password.equals(password)) {
        throw new PasswordException("密碼錯誤");
    }
    System.out.println("登錄成功"); }
}

總結:看完有什麼不懂的歡迎在下方留言評論,記得點個贊哦!