前言:博主在以前的博客中曾經也提起了例外,比如[java篇]多型,介面中在實作clone介面時,就用到了拋出例外,今天就給大家詳細的介紹一下關于例外的一些知識,
前期文章:
1.[java篇] 包,繼承,組合
2.[java篇] 多型,抽象類,介面
3.[java篇] 圖書管理系統,是你的期末大作業嗎?
4.[java篇] 幫你搞懂String,StringBuffer和StringBuilder的關系
文章目錄
- 例外背景
- 初始例外
- 防御式編程
- 例外的好處
- 例外的基本用法
- java例外體系
- 捕獲例外
- 例外的處理方法
- 拋出例外
- 例外說明
- 關于finally的一些認識
- 自定義例外
例外背景
初始例外
初學例外的友友們,會想到底什么是例外呀?
那就仔細看看下面的代碼吧!!!
public static void main(String[] args) {
System.out.println(10/0);
}
看一看代碼,10能除以0嗎,顯然不能,0不能做被除數,這是小學問題,不要給我說你不會哈,
那么在運行的欄目中就會這樣顯示:

看一看紅框里面的,里面說的是算術例外,第二個紅框框的是造成例外的原因,
在我們平時寫代碼的時候,哦,不不不,在寫bug的時候,肯定會遇到許多例外,博主就在這簡單的介紹幾個例外,
空指標例外:
public static void main(String[] args) {
int []array = {1,2,3};
array = null;
System.out.println(array[100]);
}

陣列下標越界例外:
public static void main(String[] args) {
int []array = {1,2,3};
System.out.println(array[100]);
}
陣列總共就有三個元素,是不可能讓我們訪問到下標為100的元素的,

所謂例外就是程式在運行時出現的錯誤,編譯器把運行程式時發生的錯誤通知呼叫者的一種機制,
有些童鞋就會為那在程式編譯的時候出現的是什么呀?這個就是一個錯誤,就是Error,比如說我們在寫一句列印的代碼時,不小心把一個字母給打錯了,然后編譯器就會在你寫錯的地方標注紅線,這就是在編譯的時候出現的錯誤,當然編譯器也會有打盹的時候,比如下面的一段代碼,它不會顯示紅線但是它依舊是發生錯誤的,
public static void func(){
func();
}
public static void main(String[] args) {
func();
}
很明顯,代碼的邏輯上出現了問題,在代碼進行遞回的時候沒有限制條件,造成了堆疊溢位,讓func()方法一直呼叫自己,

所以說通常我們所說的編譯時出現的錯誤,就是程式猿在寫代碼時的邏輯出現了問題,必須有自己進行修改,但是在運行時出現的例外我們可以通過例外處理就可以輕松搞定,
程式在運行的時候出現的例外有很多種,在這里就不和大家一一介紹了,下來請看我們是怎樣捕獲例外的,
防御式編程
防御式編程分為兩種:
- LBYL:在操作之前做好充分檢查,
- EAFP:事后解決比事前獲得許可更容易,簡單的就是說,先不管三七二十一直接進行操作,在操作之后再檢查哪里出現了問題,
這里利用一個通俗的例子為了大家好理解:
比如說博主現在在搞物件,有一天在街上逛街,然后博主想想牽住物件的手,說了一句,我能牽住你的手嗎?然后物件勉為其難的同意了,哭笑,這也太鋼鐵直男了吧,(這就屬于LBYL防御式編程)也只有鋼鐵直男這樣做了,正確的直接就應該順手就去牽呀,管她同不同意,不同意就算了唄,(這就屬于EAFP編程),
在我們寫程式,捕獲例外的時候一般都會選擇后者,
例外的好處
那么博主例外在那些情況下會發生呢,在這里又舉了一個例子,
比如,你在打王者榮耀的時候,突然接到了一個你物件打來的電話,這個時候你不得不接,這就屬于例外,
代碼如下:
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 (載入游戲畫面例外) {
處理載入游戲畫面例外;
}
明人不說暗話,那種代碼好看呀,當然是第二種,在EAFP狀態下,我們先進行操作,再捕獲例外,這樣在運行之后,哪里出現了例外我們一眼就可以看出,相比第一種代碼,就不怎么好辨認,所以說在我們日常撰寫代碼,捕獲例外的時候都用第二種,
例外的基本用法
java例外體系
讓我們先看一看java的例外體系,大致是這樣的,每個具體例外都是一個類

每個例外都屬于一個類,而例外又分為運行時例外和編譯時例外,同時編譯時例外和運行時例外都繼承了一個類,就是Exception類,而我們知道的空指標例外類和陣列下標越界例外類都繼承了運行時例外類,
請看下面原始碼:
空指標例外類繼承了運行時例外類

運行時例外類繼承了Exception類

Exception類又繼承了Throwable類(主類)

捕獲例外
現在就介紹一下,怎樣捕捉例外吧,它的語法是這樣的,
try{
//在這里放的是將要接受例外捕獲的代碼
}catch (//在這里放的是代碼在運行時可能出現的例外型別){
//在這里是處理例外的代碼
}finally{
//例外出口
}
- 在try代碼塊中存放的是可能出現例外的代碼
- 在catch代碼塊中存放的是代碼在出現例外時的處理
- 在finally代碼塊中存放的是例外處理完后的善后作業
- finally代碼塊和catch代碼塊可以根據代碼環境進行增添,
代碼示例一:
不檢查例外:
public static void main(String[] args) {
int []array = new int[10];
System.out.println("before");
System.out.println(array[100]);
System.out.println("after");
}
我們可以很明顯的看出,發生了陣列下標越界例外,

我們注意,在代碼運行的程序中如果檢查到了例外,代碼就會自動的交給jvm進行處理,并且結束代碼運行,正如上圖所示我們只列印了before,沒有列印到after.
代碼示例二:
我們捕獲例外后的代碼:
public static void main(String[] args) {
int []array = new int[10];
try{
System.out.println("before");
System.out.println(array[100]);
System.out.println("after");
}catch (ArrayIndexOutOfBoundsException e){
//列印出現例外的呼叫堆疊
e.printStackTrace();
System.out.println("陣列下標越界例外");
}finally {
System.out.println("1");
}
}

我們可以很明顯的看到 在try代碼塊中發現例外之后,沒有繼續執行try代碼塊中的陳述句,直接交給了catch代碼塊,檢查出現例外的型別,同時如果我們在撰寫代碼的時候,catch()括號中,不是try代碼塊中的例外型別時,就不會執行到catch代碼塊中去,就直接交給了jvm進行處理,
代碼實體三:
public static void main(String[] args) {
int []array = new int[10];
try{
System.out.println("before");
System.out.println(array[100]);
System.out.println("after");
}catch (NullPointerException e){//try代碼塊中所捕獲的例外,在catch()中不能得到處理,我們在try代碼快中捕獲的是陣列下標越界例外,但是在cathc()中處理的是空指標例外,型別不匹配
//列印出現例外的呼叫堆疊
e.printStackTrace();
System.out.println("陣列下標越界例外");
}finally {
System.out.println("1");
}
}

比如上面的這一串代碼,很明顯的看到代碼塊中出現的不是我們要的例外類,那么這個例外就由jvm進行處理,但是我們可以看到finally代碼塊中的陳述句還能執行,所以說finally代碼塊中的代碼執行時獨立的,不受try代碼塊和catch代碼塊的影響,
關于呼叫堆疊:在方法之間存在呼叫關系,這種呼叫關系我們可以用呼叫堆疊來描述,在jvm中一塊專用的記憶體空間被稱為“虛擬機堆疊”,在這個堆疊中專門用來處理方法之間的呼叫 ,當當前代碼出現例外時,我們可以利用e.printStackTracce(),來呼叫當前出現例外代碼的呼叫堆疊,
代碼示例四:當然代碼中還可能出現多種例外,我們可以同時catch多個例外
例如下面代碼:
public static void main2(String[] args) {
int []array = {1,2,3};
try{
System.out.println(array[100]);
array = null;
System.out.println(array[1]);
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
System.out.println("陣列下標越界例外");
}catch (NullPointerException e){
e.printStackTrace();
System.out.println("空指標例外");
}finally {
System.out.println("1");
}
}

一段代碼可能會出現多種例外,每種例外都有自己的處理方式,當我們在處理例外的時候采取的方式一致的時候,我們可以這樣:
public static void main(String[] args) {
int []array = {1,2,3};
try{
System.out.println(array[100]);
}catch (ArrayIndexOutOfBoundsException | NullPointerException e){
e.printStackTrace();
System.out.println("出現例外");
}finally {
System.out.println("1");
}
}
代碼示例五:當然我們可以用一個例外類,來檢查try代碼塊中的所有例外
public static void main(String[] args) {
int []array = {1,2,3};
try{
System.out.println(array[100]);
}catch (Exception e){
e.printStackTrace();
System.out.println("出現例外");
}finally {
System.out.println("1");
}
}
我們可以有上面的例外體系一覽圖可以得知,Exception類的是所有例外的父類,所以可以由Exception例外類,來查找并處理出try代碼塊中的所有例外,
備注:在catch代碼塊中進行例外匹配得時候,不僅會匹配相同的例外類,還會匹配例外類的父類
代碼示例六: 不管try代碼塊中的代碼是否出現例外,finally代碼塊中的代碼都一定會被執行,finally代碼塊用來處理善后作業,比如釋放資源,
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
int a = scanner.nextInt();
}catch (InputMismatchException e) {
e.printStackTrace();
System.out.println("發生例外");
}finally {
scanner.close();//釋放資源
}
}

代碼示例七:使用try進行回收資源
剛才代碼的等價寫法:我們可以在try()括號中,實作Scanner物件,這樣try代碼塊執行完后,自動呼叫Scanner,close()方法,
public static void main(String[] args) {
try (Scanner scanner = new Scanner(System.in)) {
int a = scanner.nextInt();
} catch (InputMismatchException e) {
e.printStackTrace();
System.out.println("發生例外");
}
}
奇淫巧技:

我們可以看到,try關鍵字背標黃了,這說明我們的代碼還可以優化,點中try關鍵字按住Alt和回車(enter)就會出現,

點擊被小燈泡標記的選項,

代碼示例八:如果在catch代碼塊中,沒有我們所匹配的例外類,我們就呼叫呼叫堆疊,依次向上呼叫,如果還是沒有那只能有jvm進行處理,
public static void func(){
Scanner scanner = new Scanner(System.in);
try{
int a = scanner.nextInt();
}catch (NullPointerException | ArrayIndexOutOfBoundsException e){
e.printStackTrace();
System.out.println("出現例外");
}finally {
System.out.println("1");
}
}
public static void main(String[] args) {
try{
func();
}catch (ArithmeticException e){
e.printStackTrace();
System.out.println("出現例外");
}
}

例外的處理方法
- 先執行try代碼塊中的代碼
- 如果try中的某行代碼出現例外,那么try代碼塊中的代碼就不會再進行執行,直接跳到catch代碼塊中,匹配到與try中例外所匹配到的例外類
- 如果匹配到了相應的例外類,那么在呼叫呼叫堆疊,列印出該例外的呼叫堆疊,
- 無論代碼是否出現了例外,finally代碼塊中的代碼都會被執行,
- 如果在匹配例外類的時候,沒有匹配到相應的例外類,那么我們就向上呼叫堆疊,直到呼叫到相應的例外類,如果還是沒有呼叫到,那么只能進入main方法,在jvm中處理例外,此時程式例外停止,
拋出例外
我們其實還可以手動的拋出例外
public static int Div(int x,int y){
if(y == 0){
throw new ArithmeticException("運算例外");
}else{
return x / y;
}
}
public static void main(String[] args) {
System.out.println(Div(10,0));
}

例外說明
通常我們在處理例外的時候,首先要知道某個方法會處理怎樣的例外,所以我們會進行標識,
public static int Div(int x,int y)throws ArithmeticException{//通過throws標記我們在查看自己代碼的時候,就很清楚的看到這個方法要處理怎樣的例外
if(y == 0){
throw new ArithmeticException("運算例外");
}else{
return x / y;
}
}
public static void main(String[] args) {
System.out.println(Div(10,0));
}
關于finally的一些認識
finally代碼塊有時還會給我們帶來許多麻煩,
比如我們在try代碼塊和finally代碼塊中,都分別return一個數字,那么最后執行,并且回傳的是finally中的代碼塊,
因為finally代碼塊是在方法回傳之前執行的(當try或catch中有return的話,會在這個return 之前執行finally中的代碼塊),當finally中的代碼塊可有return 時,是先會執行finally中的return.
所以我們在撰寫代碼的時候,不要再finally代碼塊中,進行回傳,
public static int func2(){
try{
return 10;
}finally {
return 100;
}
}
public static void main(String[] args) {
System.out.println(func2());
}
自定義例外
其實我們在日常生活中碰到的例外,肯定有許多,有些在java類別庫中是沒有的所以我們要自己實作一個例外,比如說我們在登錄某個賬戶的時候要輸入自己的姓名,和相關密碼,如果我們沒有輸入正確,就會產生例外,為輸入姓名錯誤例外,和輸入密碼錯誤例外,這就必須我們自己手動實作了,
Exception類作為所有編譯時例外和運行時例外的父類,所有的例外類都繼承他,所以我們可以設定兩個例外分別都繼承Exception類,并且呼叫父類中的構造方法,

class UserNameException extends Exception {
public UserNameException(String message) {
super(message);
}
}
class PassWorldException extends Exception {
public PassWorldException(String message) {
super(message);
}
}
public class TestDemo1 {
private static String userName = "abc";
private static String password = "123";
public static void login(String userName, String password) throws UserNameException, PassWorldException {//表明方法中要處理怎樣的例外
if(!TestDemo1.userName.equals(userName)){
throw new UserNameException("姓名輸入錯誤");//拋出例外
}
if(!TestDemo1.password.equals(password)){
throw new PassWorldException("密碼輸入錯誤");//拋出例外
}
System.out.println("輸入正確");
}
public static void main(String[] args) {
try {
login("abcd", "12");
} catch (UserNameException userNameException) {
userNameException.printStackTrace();
} catch (PassWorldException passWorldException) {
passWorldException.printStackTrace();
}
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/325567.html
標籤:java
上一篇:冒著開除的風險,讓我們一起解密學校圖書管理系統的秘密之IO流上篇
下一篇:了解Julia中的多執行緒行為
