1. 程式人生 > >你爸媽都能看懂的JAVA異常機制入門

你爸媽都能看懂的JAVA異常機制入門

目錄

@(JAVA學習筆記——超簡單的JAVA異常入門)

異常的型別

JAVA中的異常主要分為兩種,即編譯時異常執行時異常.當然,還有錯誤

,本文中對錯誤不做過多討論

編譯時異常

編譯時異常是指,在編譯時由javac編譯器(或者其他編譯器比如eclipse自帶的編譯器)報出的異常.這類異常通常是一些不需要執行就可以知道的異常.
下面用一個現實中的比方給出一個編譯時異常的例子:
比如,今天我是一個黑社會老大,我找了我的小弟(本文中指編譯器和程式),替我辦事

例子1

我給了小弟一根牙籤(請不要問我怎麼定義了這根牙籤,這不重要,反正正常人定義的牙籤一定沒有毒死人的功能)

//Toothpick是牙籤的英文,定義了一根新牙籤並把它放在牙籤盒裡(java不會天然有手、桌子這種萬能的容器,要有你必須自己造一個容器去裝它)
Toothpick y = new Toothpick();

我跟他說"用這根牙籤,給我把某個人毒死

//我告訴小弟,使用牙籤所具備的poison功能,毒死sb
y.poison(sb);

我們當然知道,牙籤,至少是正常的牙籤當然不會具備毒死人的功能,所以小弟就會告訴我說
牙籤怎麼可能毒死人?滾~

The method poison(People) is undefined for the type Toothpick

我們暫時不用著急理解這句話到底是個什麼意思,我們只需要知道,他告訴我們,我們沒告訴小弟牙籤怎麼毒死人這一點就可以了.

我們來整理一下,你讓你的小弟幹了什麼

小弟啊小弟,我要造一根牙籤
小弟啊小弟,我要請你用牙籤把sb給毒死

而小弟因為牙籤不可能毒死人,所以至少一般人認為壓根沒去辦這件事,小弟也是這麼認為的(事實上發生的事情是,小弟看過了你的功能說明書,你沒寫它可以毒死人).所以他拒絕去做.

也就是說,因為各種不符合小弟的要求的事情導致你不能編譯,那都是編譯時異常.另一個例子如下

例子2

我讓我的小弟給我建一套房子

//小弟建了一套房子
//然後把這套房子,登記到房管局House,只有這樣我們才能在以後找到這套房子
House h = new House();

然後我又跟小弟說,去,把我的房子登記在你們上下班的考勤卡上

//TimeCard是考勤卡的英文
//把之前的房子(其實不是房子,是房子的所在地的指標),登記在考勤卡里.
TimeCard t = h;

然後小弟就又開始抱怨了
考勤卡怎麼可能給你登記房地產資訊?滾~

Type mismatch: cannot convert from House to TimeCard

當然,其實這句話的資訊並不只是不能登記房產資訊這麼簡單,還帶有說他沒有辦法把你的房子變成一個可以登記在考勤表上的東西,但我們其實不著急知道這麼多,我們只需要看前半句,他的意思就是說,型別不一樣,滾~

我們再來整理一下,你這回讓你的小弟幹了什麼

小弟啊小弟,我要造一套房子
小弟啊小弟,我要請你把這套房子的資訊登記到考勤卡上

而小弟因為考勤表不能用來記房地產資訊(強記也並非完全不可能,有幾種辦法,但是這是後話),所以至少一般人認為壓根沒去辦這件事,小弟也是這麼認為的(事實上發生的事情是,小弟找了你的產品系列圖,發現房子根本不是考勤記錄的一種).所以他拒絕去做.

所以,我們的小弟壓根不打算去辦這件事情,這就是編譯時異常.

執行時異常

那麼我們再來看一下執行時異常,什麼是執行時異常呢?就是說小弟一開始還沒發現有問題,做著做著發現不對了.
下面也給出一個執行時異常的例子,還是那個假設:
今天我是一個黑社會老大,我找了我的小弟,替我辦事

例子3

我先讓小弟造了三雙鞋,s1,s2,s3然後把它們放在各自的鞋盒裡

Shoes s1 = new Shoes();
Shoes s2 = new Shoes();
Shoes s3 = new Shoes();

我先讓小弟給我造一個能放兩雙鞋的鞋櫃,然後把之前的兩雙放進去

//這裡定義了一個數組.一般情況下,java中的每個容器只能裝一樣東西,但是陣列,List,Map,Set等容器卻能裝不止一個.
//定義陣列的方法是在類的後面加[],具體陣列的知識,請專門學習陣列瞭解.
//這裡的new Shoes[2]是指這個鞋櫃只能放兩雙鞋
new Shoes[] shoes = new Shoes[2];   
//放進兩雙鞋.java或者說大多數程式語言起始於0,也就是說當計算機數數的時候,0就是1,9就是8
//這一步在鞋櫃的第一格和第二個分別放入了兩雙鞋子
shoes[0] = s1;
shoes[1] = s2;

我讓我的小弟把我的第三雙鞋也給放進鞋櫃

//乍看之下這個程式碼沒啥問題,但是,shoes[2]是指第三雙鞋
//所以這裡拿的是第三雙鞋
shoes[2]=s3;

但我說了,小弟太笨了,他不知道自己剛才才造好的鞋櫃只能放兩雙鞋,所以他會屁顛屁顛跑去看鞋櫃,然後他一看,發現:
媽耶,你這個鞋櫃根本沒有三格好嗎?滾~
所以他會丟擲一個異常說:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2

也就是說,在你的main函式的某一個鞋櫃(陣列)中,出現了一個不該出現的2號元素,而這個鞋櫃(陣列)根本沒這麼大.

老規矩,整理一下發生了什麼

小弟啊小弟,幫我造三雙鞋
小弟啊小弟,幫我造一個兩格的鞋櫃
小弟啊小弟,幫我把前兩雙鞋放進前兩個格子
小弟啊小弟,幫我把第三雙鞋放進第三個格子

然而小弟由於接收到了一個看似完整的資訊"把第三雙鞋放進第三個格子裡",就天真的以為這是OK的,於是跑到了鞋櫃前才發現鞋櫃沒這麼大
於是乎,小弟就說"這個老闆太坑了我不給他幹了,也就是說,你讓他在這之後做的事情他就都不會做了,因為程式停止運行了."(程式的執行時異常如果沒有處理,程式就會中止執行.)

例子4

我先告訴我的小弟一個數字i,但我想調戲他一下,不給他數字

//Integer是int的包裹型別,int本身並不能被賦值為null,會報編譯時異常
Integer i = null;

我讓我的小弟把我告訴他的數字加十

//這裡不能只寫i+10,因為程式會認為這個i+10不是給i用的而是給這一句中可能存在也可能不存在的其他操作用的,所以i不會變化
i=i+10;

小弟很開心的去做了,他知道你給了他一個加數i,並且還給了他一個加數10,最後給了他一個放計算結果的i,他覺得沒有任何問題,他也不管你的i具體是什麼,只要i經過了初始化就OK(初始化為空也是一種初始化)
結果,他真的算到這一步的時候,發現i是個空.這就把他惹惱了,說
媽耶,你讓我加個空是個什麼意思呀?滾~
所以他會丟擲一個異常說

Exception in thread "main" java.lang.NullPointerException

同樣不需要理解,只需要知道他在告訴你,你給了他一個空還叫他做運算.所以老規矩,罷工不幹了,這一行後面所有的其他東西都不幹了.

異常處理

最簡單的異常處理

當然,異常也是可以被處理的.在你不知道異常會不會發生的時候,你可以讓小弟去"試一試"(try),下面給出一個例子

例子5

繼續延續例子3(放鞋)
現在我讓小弟嘗試一下有沒有第三個格子

//嘗試大括號裡的事情
try{
    shoes[2]=s3;
}

當然光是這樣編譯器還是會報錯的,因為你只叫他嘗試一下,這跟啥也沒說沒啥區別,必須要告訴他失敗了怎麼辦
所以你得告訴他比如:
你去試一下,不管因為什麼原因沒成功就跳過這個步驟(什麼都不做)

try{
    shoes[2]=s3;
    //捕獲一個異常,這裡的Exception是指任意的執行時異常,這裡的e是指大多數異常會留下的黑匣子,我們暫且不管黑匣子能幹嘛
}catch(Exception e){
    //大括號裡啥也沒有,意味著什麼都不做,跳到下一條語句
}

這樣執行之後,出錯時就會跳過了.你也可以在括號裡多加一些東西,要注意的是,所有放在try裡的語句,不管哪句出錯都會跳到後面的catch語句,而try塊裡後面其他的任何語句即使不出錯也都不執行.

捕捉特定的異常

但是,正如我們之前說的,異常不止一種,而每種異常都不一樣.我們有時候只能處理其中的一部分異常,而另外的一部分我們沒辦法處理,因此,我們也可以只丟擲一部分異常,剩下一些我們不管,比如我們把前面兩個例子合併一下:

例子6

Integer[] in = new Integer[2];
Integer i = null;
in[2] = i+10;

正常來說,這個東西的第三行會告訴我們兩個錯誤,既有陣列下標越界(鞋櫃沒有第三格),也有空指標(數字不存在)
假設我們只想處理數字不存在,至於鞋櫃有多大我可不管
那麼,我們就可以告訴小弟:
你去試一試,如果沒找到數字就用3(但是其他錯誤我也不知道該幹嘛)

//這個櫃子不再裝鞋子了,他開始裝整數了
Integer[] in = new Integer[2];
Integer i = null;
try{
    in[2] = i+10;
    //只捕獲空指標異常,這裡的Exception是指任意的執行時異常,這裡的e是指大多數異常會留下的黑匣子,我們暫且不管黑匣子能幹嘛
}catch(NullPointerException e){
    //在整數櫃(除了放進去的是一個整數之外基本上跟鞋櫃一個道理)裡放進一個3
    in[2] = 3;
}

當然,這個地方真的要執行的話,小弟給了3還是會報錯的,他會說
你只告訴我沒找到數字咋辦,你可沒告訴我沒格子咋辦,那我不管了,我出錯了我就不給你辦事了

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2

但它至少處理掉了我們的空指標.這個步驟的作用我們在下文會繼續討論

定義和丟擲一個異常

我們可以定義一個異常類,去宣告一個異常,當然它也可以有一些資訊,但我們只為了說明白異常是什麼做一些最簡單的試驗,暫時先不管複雜的

例子7

我讓我的小弟給我的一個員工(函式)20塊錢,讓這個員工(函式)去旁邊的超市買一個葫蘆娃

//buy是一個函式,相當於你的一個員工,你告訴他一些資訊或者給他一些東西,他幫你辦一件事情然後給你一個(唯一的)回信
buy(20,"葫蘆娃");

由於小弟很笨,他就是現實社會中這種只管接受形式條件而不管它是什麼的人,他只管他收到了你要他買的東西的名稱("葫蘆娃")就屁顛屁顛去了,這個員工也屁顛屁顛去了.至於有沒有你說的葫蘆娃他才不幫你判斷呢,結果店老闆一看,葫蘆娃是個什麼玩意?不知道,滾~(雖然很多老闆會直接拋異常,但我們這裡先認為他返回了一個null)
於是小弟就屁顛屁顛帶著一個null回來了,說它是六娃(會隱身的那個)(在java中,大多數時候null可以屬於任何一個類,這裡null也是葫蘆娃的一種)
然後呢,你就看著空氣哭吧
這時候怎麼辦呢,你就要告訴員工,如果沒有我要買的東西咋辦
比如,你告訴員工
你去試著買一下,如果老闆說賣完了你就讓他進貨

try{
buy(20,"葫蘆娃");
//Stockout是缺貨的英文
}catch(StockoutException e){
    //stock是進貨
    stock("葫蘆娃");
}

當然,這樣還是不行的,因為相比於其他的執行時異常,JAVA可不知道你說的這個StockoutException是個啥,所以你要定義一個StockoutException,告訴你的員工和老闆,什麼是一個StockoutException.

//這段寫在StockoutException.java中
//extends Exception是指它是異常類的一個子類.只有異常類的子類我們才叫它異常
public class StockoutException extends Exception{
}

並且告訴老闆:
如果沒貨了就拋給顧客一個StockoutException

throw new StockoutException();

當然你會發現這樣還是不行,因為老闆的思維只有一條線,它才不會管是不是你的員工來買,不管誰來買,只要沒貨了就會丟擲這個異常.
所以,老闆得這樣告訴所有人:
你來買我就有可能拋給你一個StockoutException
所以,buy函式可能得這樣定義

//throws不同於throw,throw是說讓它丟擲,throws是說我有可能會丟擲.類似於英語上的第三人稱單數
buy(int money, String toBuy) throws StockoutException

所以,所有買它的人也會了解到並且想辦法處理這個異常,要麼用try的方法嘗試,要麼:
跟你的大哥(比如,這個例子中對於員工來說,他的大哥就是你,而對於老闆來說,他的大哥就是你的員工)說:啊,我執行到一個StockoutException
即在函式定義中加上

throws StockoutException

需要注意的是,如果main函式接收到了這個異常並且還是沒有處理,那程式就會中止.

處理多種異常

當然,我們也可以處理不止一種錯誤,還是舉一個例子

例子8

繼續剛才買葫蘆娃的例子,有沒有可能不是缺貨而是沒錢呢?當然有,所以我們要想辦法讓員工更厲害一點,所以我們再定義一個PoorException(由於原理相似,我就不講怎麼讓老闆和員工知道PoorException和如何讓老闆丟擲這個異常了)並且讓員工處理它.
我們告訴員工:
你去拿著這20塊錢買個葫蘆娃,如果沒有葫蘆娃,就讓老闆進貨;如果錢不夠(PoorException)就去討飯

try{
buy(20,"葫蘆娃");
}catch(StockoutException e){
    stock("葫蘆娃");
//兩個名稱可以一樣,因為不會同時抓到
}catch(PoorException e){
    //討飯
    begForFood();
}

所以,如果員工沒錢了,就會去討飯

當然,我們還可以給個兜底的,比如:
你去拿著這20塊錢買個葫蘆娃,如果沒有葫蘆娃,就讓老闆進貨;如果錢不夠(PoorException)就去討飯,如果有其它問題,就回來
程式碼如下:

try{
buy(20,"葫蘆娃");
}catch(StockoutException e){
    stock("葫蘆娃");
//兩個名稱可以一樣,因為不會同時抓到
}catch(PoorException e){
    //討飯
    begForFood();
}catch(Exception e){
    //回來向我報告,如果我們指定讓員工帶報告回來,他還必須帶來同格式的報告,如return -1;
    return;
}

需要注意的是,catch是有順序的,如果前一條語句catch到了後一條catch就不會執行了,這就好比你告訴員工
給你20塊去買個葫蘆娃,如果發生問題馬上回來,如果老闆沒貨了就讓他進貨
他不會看最後一部分,他一看到"發生問題馬上回來"就已經喜笑顏開了,然後就跑回來了

寫累了,留給各位初學java的同學們,希望還能有用,有問題的話可以發郵件到我的qq郵箱[email protected],我會盡快更正.