Java基礎(chǔ)知識(5)-- 異常處理

異常處理機(jī)制能讓程序在異常發(fā)生時,按照代碼的預(yù)先設(shè)定的異常處理邏輯,針對性地處理異常,讓程序盡最大可能恢復(fù)正常并繼續(xù)執(zhí)行,且保持代碼的清晰。

Java中的異??梢允呛瘮?shù)中的語句執(zhí)行時引發(fā)的,也可以是程序員通過throw 語句手動拋出的,只要在Java程序中產(chǎn)生了異常,就會用一個對應(yīng)類型的異常對象來封裝異常,JRE就會試圖尋找異常處理程序來處理異常。

Throwable類是Java異常類型的頂層父類,一個對象只有是 Throwable 類的(直接或者間接)實(shí)例,他才是一個異常對象,才能被異常處理機(jī)制識別。JDK中內(nèi)建了一些常用的異常類,我們也可以自定義異常。

Throwable又派生出Error類和Exception類。

錯誤:Error類以及他的子類的實(shí)例,代表了JVM本身的錯誤。錯誤不能被程序員通過代碼處理,Error很少出現(xiàn)。因此,程序員應(yīng)該關(guān)注Exception為父類的分支下的各種異常類。

異常:Exception以及他的子類,代表程序運(yùn)行時發(fā)送的各種不期望發(fā)生的事件??梢灶A(yù)料的意外情況,可能并且應(yīng)該被捕獲,進(jìn)行相應(yīng)處理,可以被Java異常處理機(jī)制使用,是異常處理的核心。


總體上我們根據(jù)Javac對異常的處理要求,將異常類分為2類:

非檢查異常(unckecked exception):Error 和 RuntimeException 以及他們的子類。javac在編譯時,不會提示和發(fā)現(xiàn)這樣的異常,不要求在程序處理這些異常。所以如果愿意,我們可以編寫代碼處理(使用try…catch…finally)這樣的異常,也可以不處理。對于這些異常,我們應(yīng)該修正代碼,而不是去通過異常處理器處理 。這樣的異常發(fā)生的原因多半是代碼寫的有問題。

檢查異常(checked exception):除了Error 和 RuntimeException的其它異常。javac強(qiáng)制要求程序員為這樣的異常做預(yù)備處理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch語句捕獲它并處理,要么用throws子句聲明拋出它,否則編譯不會通過。這樣的異常一般是由程序的運(yùn)行環(huán)境導(dǎo)致的。因?yàn)槌绦蚩赡鼙贿\(yùn)行在各種未知的環(huán)境下,而程序員無法干預(yù)用戶如何使用他編寫的程序,于是程序員就應(yīng)該為這樣的異常時刻準(zhǔn)備著。

在編寫代碼處理異常時,對于檢查異常,有2種不同的處理方式:使用try…catch…finally語句塊處理它。或者,在函數(shù)簽名中使用throws 聲明交給函數(shù)調(diào)用者caller去解決。

1try…catch…finally語句塊:

try

{

???? //try塊中放可能發(fā)生異常的代碼。

???? //如果執(zhí)行完try且不發(fā)生異常,則接著去執(zhí)行finally塊和finally后面的代碼(如果有的話)。

???? //如果發(fā)生異常,則嘗試去匹配catch塊。

}

catch (SQLException SQLexception)

{

????//每一個catch塊用于捕獲并處理一個特定的異常,或者這異常類型的子類。Java7中可以將多個異常聲明在一個catch中。

????//catch后面的括號定義了異常類型和異常參數(shù)。如果異常與之匹配且是最先匹配到的,則虛擬機(jī)將使用這個catch塊來處理 異常。

????//在catch塊中可以使用這個塊的異常參數(shù)來獲取異常的相關(guān)信息。異常參數(shù)是這個catch塊中的局部變量,其它塊不能訪問。

????//如果當(dāng)前try塊中發(fā)生的異常在后續(xù)的所有catch中都沒捕獲到,則先去執(zhí)行finally,然后到這個函數(shù)的外部caller中去匹配異常處理器。

??? //如果try中沒有發(fā)生異常,則所有的catch塊將被忽略。

}

catch (Exception exception)

{

??? //...

}

finally

{

? ? //finally塊通常是可選的。

?? //無論異常是否發(fā)生,異常是否匹配被處理,finally都會執(zhí)行。

?? //一個try至少要有一個catch塊,否則, 至少要有1個finally塊。但是finally不是用來處理異常的,finally不會捕獲異常。

? //finally主要做一些清理工作,如流的關(guān)閉,數(shù)據(jù)庫連接的關(guān)閉等。

}


需要注意的地方

1、try塊中的局部變量和catch塊中的局部變量(包括異常變量),以及finally中的局部變量,他們之間不可共享使用。

2、每一個catch塊用于處理一個異常。異常匹配是按照catch塊的順序從上往下尋找的,只有第一個匹配的catch會得到執(zhí)行。匹配時,不僅運(yùn)行精確匹配,也支持父類匹配,因此,如果同一個try塊下的多個catch異常類型有父子關(guān)系,應(yīng)該將子類異常放在前面,父類異常放在后面,這樣保證每個catch塊都有存在的意義。

3、java中,異常處理的任務(wù)就是將執(zhí)行控制流從異常發(fā)生的地方轉(zhuǎn)移到能夠處理這種異常的地方去。也就是說:當(dāng)一個函數(shù)的某條語句發(fā)生異常時,這條語句的后面的語句不會再執(zhí)行,它失去了焦點(diǎn)。執(zhí)行流跳轉(zhuǎn)到最近的匹配的異常處理catch代碼塊去執(zhí)行,異常被處理完后,執(zhí)行流會接著在“處理了這個異常的catch代碼塊”后面接著執(zhí)行。


2throws 函數(shù)聲明

????? throws聲明:如果一個方法內(nèi)部的代碼會拋出檢查異常(checked exception),而方法自己又沒有完全處理掉,則javac保證你必須在方法的簽名上使用throws關(guān)鍵字聲明這些可能拋出的異常,否則編譯不通過。

throws是另一種處理異常的方式,它不同于try…catch…finally,throws僅僅是將函數(shù)中可能出現(xiàn)的異常向調(diào)用者聲明,而自己則不具體處理。

采取這種異常處理的原因可能是:方法本身不知道如何處理這樣的異常,或者說讓調(diào)用者處理更好,調(diào)用者需要為可能發(fā)生的異常負(fù)責(zé)。

public void foo() throws ExceptionType1, ExceptionType2, ExceptionTypeN

{

???? //foo內(nèi)部可以拋出 ExceptionType1 , ExceptionType2, ExceptionTypeN 類的異常,或者他們的子類的異常對象。

}


finally塊和return

首先一個不容易理解的事實(shí):在 try塊中即便有return,break,continue等改變執(zhí)行流的語句,finally也會執(zhí)行。

也就是說:try…catch…finally中的return只要能執(zhí)行,就都執(zhí)行了,他們共同向同一個內(nèi)存地址(假設(shè)地址是0×80)寫入返回值,后執(zhí)行的將覆蓋先執(zhí)行的數(shù)據(jù),而真正被調(diào)用者取的返回值就是最后一次寫入的。

根據(jù)上述推斷下面結(jié)論就比較容易理解:

情況一:如果finally中有return語句,則會將try中的return語句”覆蓋“掉,直接執(zhí)行finally中的return語句,得到返回值,這樣便無法得到try之前保留好的返回值。

情況二:如果finally中沒有return語句,也沒有改變要返回值,則執(zhí)行完finally中的語句后,會接著執(zhí)行try中的return語句,返回之前保留的值。

情況三:如果finally中沒有return語句,但是改變了要返回的值,這里有點(diǎn)類似于引用傳遞和值傳遞的區(qū)別,分以下兩種情況:

? ? ? ? ?* 如果return的數(shù)據(jù)是基本數(shù)據(jù)類型,則在finally中對該基本數(shù)據(jù)的改變不起作用,try中的return語句依然會返回進(jìn)入finally塊之前保留的值。

? ? ? ? * 如果return的數(shù)據(jù)是引用數(shù)據(jù)類型,而在finally中對該引用數(shù)據(jù)類型的屬性值的改變起作用,try中的return語句返回的就是在finally中改變后的該屬性的值。


public class ExceptionTest {

? ? public static void main(String[] args)

? ? {

? ? ? ? ExceptionTest test = new ExceptionTest();

? ? ? ? System.out.println(test.fun());

? ? ? ? System.out.println(test.fun1());

? ? ? ? System.out.println(test.fun2());

? ? }

// 情況一:如果finally中有return語句,則會將try中的return語句”覆蓋“掉,直接執(zhí)行finally中的return語句,得到返回值,這樣便無法得到try之前保留好的返回值。

? ? public int fun()

? ? {

? ? ? ? int i = 10;

? ? ? ? try

? ? ? ? {

? ? ? ? ? ? //doing something

? ? ? ? ? ? return i;

? ? ? ? }catch(Exception e){

? ? ? ? ? ? return i;

? ? ? ? }finally{

? ? ? ? ? ? i = 20;

? ? ? ? ? ? return i;

? ? ? ? }

? ? }

輸出結(jié)果:20

// 情況二:如果finally中沒有return語句,但是改變了要返回的值,這里有點(diǎn)類似于引用傳遞和值傳遞的區(qū)別,分以下兩種情況:

// 如果return的數(shù)據(jù)是基本數(shù)據(jù)類型,則在finally中對該基本數(shù)據(jù)的改變不起作用,try中的return語句依然會返回進(jìn)入finally塊之前保留的值。

? ? public int fun1()

? ? {

? ? ? ? int i = 10;

? ? ? ? try

? ? ? ? {

? ? ? ? ? ? //doing something

? ? ? ? ? ? return i;

? ? ? ? }catch(Exception e){

? ? ? ? ? ? return i;

? ? ? ? }finally{

? ? ? ? ? ? i = 20;

? ? ? ? }

? ? }

輸出結(jié)果:10

// 如果return的數(shù)據(jù)是引用數(shù)據(jù)類型,而在finally中對該引用數(shù)據(jù)類型的屬性值的改變起作用,try中的return語句返回的就是在finally中改變后的該屬性的值。

? ? public StringBuilder fun2()

? ? {

? ? ? ? StringBuilder s = new StringBuilder("Hello");

? ? ? ? try

? ? ? ? {

? ? ? ? ? ? //doing something

? ? ? ? ? ? s.append("Word");

? ? ? ? ? ? return s;

? ? ? ? }catch(Exception e){

? ? ? ? ? ? return s;

? ? ? ? }finally{

? ? ? ? ? ? s.append("finally");

? ? ? ? }

? ? }

}

輸出結(jié)果:HelloWordfinally

總結(jié)一下:其實(shí)return與finally并沒有明顯的誰強(qiáng)誰弱。在執(zhí)行時,是return語句先把返回值寫入但內(nèi)存中,然后停下來等待finally語句塊執(zhí)行完,return再執(zhí)行后面的一段。


建議:

* 不要在fianlly中使用return。

* 不要在finally中拋出異常。

* 減輕finally的任務(wù),不要在finally中做一些其它的事情,finally塊僅僅用來釋放資源是最合適的。

* 盡量將所有的return寫在函數(shù)的最后面,而不是try … catch … finally中。

? ? ? ?備注:在 finally 塊中拋出的任何異常都會覆蓋掉在其前面由 try 或者 catch 塊拋出異常。包含 return 語句的情形相似。


11Java異常處理的最佳實(shí)踐

1)不要在catch語句塊中壓制異常

public class ExceptionExample {

??? public FileInputStreamtestMethod1(){

??????? File file = newFile("test.txt");

??????? FileInputStreamfileInputStream = null;

??????? try{

??????????? fileInputStream= new FileInputStream(file);

???????????fileInputStream.read();

??????? }catch (IOExceptione){????????

??????????? return null;

??????? }

??????? return fileInputStream;

??? }

??? public static void main(String[] args){

??????? ExceptionExampleinstance1 = new ExceptionExample();

???????instance1.testMethod1();

??? }

}???

在異常處理時進(jìn)行異常壓制是非常不好的編程習(xí)慣,上面的例子中,無論拋出什么異常都會被忽略,以至沒有留下任何問題線索。如果在這一層次不知道如何處理異常,最好將異常重新拋出,由上層決定如何處理異常。

public class ExceptionExample {

??? public FileInputStreamtestMethod1() throws IOException{

??????? File file = newFile("test.txt");

??????? FileInputStreamfileInputStream = null;

??????? try{

??????????? fileInputStream= new FileInputStream(file);

???????????fileInputStream.read();

??????? }catch (IOExceptione){????????

??????????? throw e;

??????? }

??????? returnfileInputStream;

??? }

??? public static voidmain(String[] args) throws IOException{

??????? ExceptionExampleinstance1 = new ExceptionExample();

???????instance1.testMethod1();

??? }

}


2)要在方法定義分句中定義具體的異常

按照public FileInputStream testMethod1() throws Exception{這種寫法,表示該方法會拋出所有受檢查異常,這不是一個良好的編程習(xí)慣。在這種情況下,我們最好拋出足夠具體的異常,以便調(diào)用者進(jìn)行合適的捕獲和處理,例如public FileInputStream testMethod1() throws IOException。


3)捕獲具體的異常

在調(diào)用其他模塊時,最好捕獲由該模塊拋出的具體的異常。如果某個被調(diào)用模塊拋出了多個異常,那么只捕獲這些異常的父類是不好的編程習(xí)慣。

例如,如果一個模塊拋出FileNotFoundException和IOException,那么調(diào)用這個模塊的代碼最好寫兩個catch語句塊分別捕獲這兩個異常,而不要只寫一個捕獲Exception的catch語句塊。

正確的寫法如下:

try {

?? //some statements

catch(FileNotFoundException e){

//handle here

}

catch(IOException e){

//handle here

}


你最好不要這么寫:

try {

?? //some statements

catch(Exception e){

//handle here

}


4)記得在finally語句塊中釋放資源

當(dāng)你在代碼中建立了數(shù)據(jù)庫連接、文件操作符或者其他需要被及時釋放的系統(tǒng)資源,如果你沒有及時釋放這些資源,會影響到系統(tǒng)的性能。

為了避免這種情況發(fā)生,可以使用Java 7的try(open the resources) {deal with resources}語句,如果你還是習(xí)慣這種老式寫法,則可以按照如下方式寫:

finally {

??? try {

??????? if (con != null) {

??????????? con.close();

??????? }

??????? if (stat != null) {

??????????? stat.close();

??????? }

??? } catch (SQLExceptionsqlee) {

???????sqlee.printStackTrace();

??? }

}


5)異常會影響性能

異常處理的性能成本非常高,每個Java程序員在開發(fā)時都應(yīng)牢記這句話。創(chuàng)建一個異常非常慢,拋出一個異常又會消耗1~5ms,當(dāng)一個異常在應(yīng)用的多個層級之間傳遞時,會拖累整個應(yīng)用的性能。

僅在異常情況下使用異常;

在可恢復(fù)的異常情況下使用異常;

盡管使用異常有利于Java開發(fā),但是在應(yīng)用中最好不要捕獲太多的調(diào)用棧,因?yàn)樵诤芏嗲闆r下都不需要打印調(diào)用棧就知道哪里出錯了。因此,異常消息應(yīng)該提供恰到好處的信息。


6)使用標(biāo)準(zhǔn)異常

如果使用內(nèi)建的異常可以解決問題,就不要定義自己的異常。Java API提供了上百種針對不同情況的異常類型,在開發(fā)中首先盡可能使用Java API提供的異常,如果標(biāo)準(zhǔn)的異常不能滿足你的要求,這時候創(chuàng)建自己的定制異常。盡可能得使用標(biāo)準(zhǔn)異常有利于新加入的開發(fā)者看懂項(xiàng)目代碼。


7)正確的包裝異常類型

當(dāng)需要在應(yīng)用重新拋出異常時,應(yīng)該正確得包裝原始異常,否則會丟失原始異常,例如下面的例子中:

import java.io.IOException;

public class HelloWorld{

???? public static voidmain(String []args) throws Exception{

??????? try{

? ??????????throw newIOException("IOException");???

??????? }catch (IOExceptione){

??????????? throw newExampleException1("Example Exception and " + e.getMessage());

??????? }

? ? ?}

}


class ExampleException1 extends Exception{

??? public ExampleException1(Strings, Throwable t){

??????? super(s,t);

??? }

??? publicExampleException1(String s){

??????? super(s);

??? }

}

這個程序的輸出為:

Exception in thread "main" ExampleException1: ExampleException and IOException?????????????????????????????????????????????????????????????????????????????????????????

??????? atHelloWorld.main(HelloWorld.java:8)??????????????????

這里發(fā)現(xiàn),IOException的調(diào)用棧已經(jīng)丟失了,因?yàn)槲覀冊赾atch語句塊中沒有正確包裝IOException。若將catch語句塊修改成下面這樣,這可以發(fā)現(xiàn)原始異常的調(diào)用棧也被打印出來了。


catch (IOException e){

??? throw newExampleException1("Example Exception",e);

}

這時候的輸出如下:

Exception in thread "main" ExampleException1: ExampleException????????????? ??????????????????????????????????????????????????????????????????????????????????

??????? atHelloWorld.main(HelloWorld.java:8)???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????

Caused by: java.io.IOException: IOException?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????

??????? atHelloWorld.main(HelloWorld.java:6)?????????


8)避免在finally語句塊中拋出異常

try {

? method();? //here throws first exception

} finally {

? shutdown(); //If finallyblockthrew any exception the first exception will be lost forever

}

在上面的這個代碼片段中,finally代碼塊也可能再次拋出異常。如果同時拋出兩個異常,則第一個異常的調(diào)用棧會丟失。在finally語句塊中最好只做打印錯誤信息或者關(guān)閉資源等操作,避免在finally語句塊中再次拋出異常。


9)不要使用異??刂瞥绦虻牧鞒?/b>

不應(yīng)該使用異常控制應(yīng)用的執(zhí)行流程,例如,本應(yīng)該使用if語句進(jìn)行條件判斷的情況下,你卻使用異常處理,這是非常不好的習(xí)慣,會嚴(yán)重影響應(yīng)用的性能。


10)不要捕獲Throwable類

在應(yīng)用中不應(yīng)捕獲Throwable類,Error是Throwable類的子類,當(dāng)應(yīng)用拋出Errors的時候,一般都是不可恢復(fù)的情況。


11)為異常記錄合適的文檔

為應(yīng)用中定義的異常定義合適的文檔,如果你寫了一個自定義的異常卻沒有文檔,其他開發(fā)者會不清楚這個異常的含義,為你定義的異常配備對應(yīng)的文檔是一個非常好的習(xí)慣。


關(guān)于NoClassDefFoundError和ClassNotFoundException異常

java.lang.NoClassDefFoundError 和 java.lang.ClassNotFoundException 都是 Java 語言定義的標(biāo)準(zhǔn)異常。從異常類的名稱看似乎都跟類的定義找不到有關(guān),但是還是有些差異。我們先來看一下 java 規(guī)范中對這兩個異常的說明:

java.lang.NoClassDefFoundError:

出現(xiàn)這個異常,并不是說這個.class類文件不存在,而是類加載器試圖加載類的定義時卻找不到這個類的定義,實(shí)際上.class文件是存在的。

java.lang.ClassNotFoundException:

從規(guī)范說明看, java.lang.ClassNotFoundException 異常拋出的根本原因是類文件找不到,缺少了.class 文件,比如少引了某個 jar,解決方法通常需要檢查一下 classpath 下能不能找到包含缺失 .class 文件的 jar。

但是,很多人在碰到 java.lang.NoClassDefFoundError 異常時也會下意識的去檢查是不是缺少了.class 文件,比如 SO 上的這位提問者(java.lang.NoClassDefFoundError: Couldnot initialize class XXX)-- “明明 classpath 下有那個 jar 為什么還報這個異?!啊6鴮?shí)際上,這個異常的來源根本不是因?yàn)槿鄙?.class 文件。而碰到這個異常的解決辦法,一般需要檢查這個類定義中的初始化部分如類屬性定義、static 塊等)的代碼是否有拋異常的可能。


如果是 static 塊,可以考慮在其中將異常捕獲并打印堆棧等,或者直接在對類進(jìn)行初始化調(diào)用(如 new Foobar())時作try? catch。

static{

  try{

    ... your init code here

  }catch(Throwable t){

    LOG.error("Failure during static initialization", t);

  }

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容