java 异常 理解_java异常理解(1)
JAVA異常處理機制
1引子
try…catch…finally恐怕是大家再熟悉不過的語句了,而且感覺用起來也是很簡單,邏輯上似乎也是很容易理解。不過,我親自體驗的“教訓”告訴我,這個東西可不是想象中的那么簡單、聽話。不信?那你看看下面的代碼,“猜猜”它執行后的結果會是什么?不要往后看答案、也不許執行代碼看真正答案哦。如果你的答案是正確,那么這篇文章你就不用浪費時間看啦。
package myExample.testException;
public class TestException {
public TestException() {
}
boolean testEx() throws Exception{
boolean ret = true;
try{
ret = testEx1();
}catch (Exception e){
System.out.println("testEx, catch exception");
ret = false;
throw e;
}finally{
System.out.println("testEx, finally; return value="+ret);
return ret;
}
}
boolean testEx1() throws Exception{
boolean ret = true;
try{
ret = testEx2();
if (!ret){
return false;
}
System.out.println("testEx1, at the end of try");
return ret;
}catch (Exception e){
System.out.println("testEx1, catch exception");
ret = false;
throw e;
}
finally{
System.out.println("testEx1, finally; return value="+ret);
return ret;
}
}
boolean testEx2() throws Exception{
boolean ret = true;
try{
int b=12;
int c;
for (int i=2;i>=-2;i--){
c=b/i;
System.out.println("i="+i);
}
return true;
}catch (Exception e){
System.out.println("testEx2, catch exception");
ret = false;
throw e;
}
finally{
System.out.println("testEx2, finally; return value="+ret);
return ret;
}
}
public static void main(String[] args) {
TestException testException1 = new TestException();
try{
testException1.testEx();
}catch(Exception e){
e.printStackTrace();
}
}
}
你的答案是什么?是下面的答案嗎?
i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, catch exception
testEx1, finally; return value=false
testEx, catch exception
testEx, finally; return value=false
如果你的答案真的如上面所說,那么你錯啦。^_^,那就建議你仔細看一看這篇文章或者拿上面的代碼按各種不同的情況修改、執行、測試,你會發現有很多事情不是原來想象中的那么簡單的。
現在公布正確答案:
i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, finally; return value=false
testEx, finally; return value=false
2基礎知識
2.1相關概念
例外是在程序運行過程中發生的異常事件,比如除0溢出、數組越界、文件找不到等,這些事件的發生將阻止程序的正常運行。為了加強程序的魯棒性,程序設計時,必須考慮到可能發生的異常事件并做出相應的處理。C語言中,通過使用if語句來判斷是否出現了例外,同時,調用函數通過被調用函數的返回值感知在被調用函數中產生的例外事件并進行處理。全程變量ErroNo常常用來反映一個異常事件的類型。但是,這種錯誤處理機制會導致不少問題。
Java通過面向對象的方法來處理例外。在一個方法的運行過程中,如果發生了例外,則這個方法生成代表該例外的一個對象,并把它交給運行時系統,運行時系統尋找相應的代碼來處理這一例外。我們把生成例外對象并把它提交給運行時系統的過程稱為拋棄(throw)一個例外。運行時系統在方法的調用棧中查找,從生成例外的方法開始進行回朔,直到找到包含相應例外處理的方法為止,這一個過程稱為捕獲(catch)一個例外。
2.2Throwable類及其子類
用面向對象的方法處理例外,就必須建立類的層次。類Throwable位于這一類層次的最頂層,只有它的后代才可以做為一個例外被拋棄。圖1表示了例外處理的類層次。
從圖中可以看出,類Throwable有兩個直接子類:Error和Exception。Error類對象(如動態連接錯誤等),由Java虛擬機生成并拋棄(通常,Java程序不對這類例外進行處理);Exception類對象是Java程序處理或拋棄的對象。它有各種不同的子類分別對應于不同類型的例外。其中類RuntimeException代表運行時由Java虛擬機生成的例外,如算術運算例外ArithmeticException(由除0錯等導致)、數組越界例外ArrayIndexOutOfBoundsException等;其它則為非運行時例外,如輸入輸出例外IOException等。Java編譯器要求Java程序必須捕獲或聲明所有的非運行時例外,但對運行時例外可以不做處理。
圖1例外處理的類層次
2.3異常處理關鍵字
Java的異常處理是通過5個關鍵字來實現的:try,catch,throw,throws,finally。JB的在線幫助中對這幾個關鍵字是這樣解釋的:
Throws:Lists the exceptions a method could throw.
Throw:Transfers control of the method to the exception handler.
Try:Opening exception-handling statement.
Catch:Captures the exception.
Finally:Runs its code before terminating the program.
2.3.1try語句
try語句用大括號{}指定了一段代碼,該段代碼可能會拋棄一個或多個例外。
2.3.2catch語句
catch語句的參數類似于方法的聲明,包括一個例外類型和一個例外對象。例外類型必須為Throwable類的子類,它指明了catch語句所處理的例外類型,例外對象則由運行時系統在try所指定的代碼塊中生成并被捕獲,大括號中包含對象的處理,其中可以調用對象的方法。
catch語句可以有多個,分別處理不同類的例外。Java運行時系統從上到下分別對每個catch語句處理的例外類型進行檢測,直到找到類型相匹配的catch語句為止。這里,類型匹配指catch所處理的例外類型與生成的例外對象的類型完全一致或者是它的父類,因此,catch語句的排列順序應該是從特殊到一般。
也可以用一個catch語句處理多個例外類型,這時它的例外類型參數應該是這多個例外類型的父類,程序設計中要根據具體的情況來選擇catch語句的例外處理類型。
2.3.3finally語句
try所限定的代碼中,當拋棄一個例外時,其后的代碼不會被執行。通過finally語句可以指定一塊代碼。無論try所指定的程序塊中拋棄或不拋棄例外,也無論catch語句的例外類型是否與所拋棄的例外的類型一致,finally所指定的代碼都要被執行,它提供了統一的出口。通常在finally語句中可以進行資源的清除工作。如關閉打開的文件等。
2.3.4throws語句
throws總是出現在一個函數頭中,用來標明該成員函數可能拋出的各種異常。對大多數Exception子類來說,Java編譯器會強迫你聲明在一個成員函數中拋出的異常的類型。如果異常的類型是Error或RuntimeException,或它們的子類,這個規則不起作用,因為這在程序的正常部分中是不期待出現的。如果你想明確地拋出一個RuntimeException,你必須用throws語句來聲明它的類型。
2.3.5throw語句
throw總是出現在函數體中,用來拋出一個異常。程序會在throw語句后立即終止,它后面的語句執行不到,然后在包含它的所有try塊中(可能在上層調用函數中)從里向外尋找含有與其匹配的catch子句的try塊。
3關鍵字及其中語句流程詳解
3.1try的嵌套
你可以在一個成員函數調用的外面寫一個try語句,在這個成員函數內部,寫另一個try語句保護其他代碼。每當遇到一個try語句,異常的框架就放到堆棧上面,直到所有的try語句都完成。如果下一級的try語句沒有對某種異常進行處理,堆棧就會展開,直到遇到有處理這種異常的try語句。下面是一個try語句嵌套的例子。
class MultiNest {
static void procedure() {
try {
int a = 0;
int b = 42/a;
} catch(java.lang.ArithmeticException e) {
System.out.println("in procedure, catch ArithmeticException: " + e);
}
}
public static void main(String args[]) {
try {
procedure();
} catch(java.lang. Exception e) {
System.out.println("in main, catch Exception: " + e);
}
}
}
這個例子執行的結果為:
in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero
成員函數procedure里有自己的try/catch控制,所以main不用去處理ArrayIndexOutOfBoundsException;當然如果如同最開始我們做測試的例子一樣,在procedure中catch到異常時使用throw e;語句將異常拋出,那么main當然還是能夠捕捉并處理這個procedure拋出來的異常。例如在procedure函數的catch中的System.out語句后面增加throw e;語句之后,執行結果就變為:
in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero
in main, catch Exception: java.lang.ArithmeticException: / by zero
3.2try-catch程序塊的執行流程以及執行結果
相對于try-catch-finally程序塊而言,try-catch的執行流程以及執行結果還是比較簡單的。
首先執行的是try語句塊中的語句,這時可能會有以下三種情況:
1.如果try塊中所有語句正常執行完畢,那么就不會有其他的“動做”被執行,整個try-catch程序塊正常完成。
2.如果try語句塊在執行過程中碰到異常V,這時又分為兩種情況進行處理:
2如果異常V能夠被與try相應的catch塊catch到,那么第一個catch到這個異常的catch塊(也是離try最近的一個與異常V匹配的catch塊)將被執行;如果catch塊執行正常,那么try-catch程序塊的結果就是“正常完成”;如果該catch塊由于原因R突然中止,那么try-catch程序塊的結果就是“由于原因R突然中止(completes abruptly)”。
2如果異常V沒有catch塊與之匹配,那么這個try-catch程序塊的結果就是“由于拋出異常V而突然中止(completes abruptly)”。
3.如果try由于其他原因R突然中止(completes abruptly),那么這個try-catch程序塊的結果就是“由于原因R突然中止(completes abruptly)”。
3.3try-catch-finally程序塊的執行流程以及執行結果
try-catch-finally程序塊的執行流程以及執行結果比較復雜。
首先執行的是try語句塊中的語句,這時可能會有以下三種情況:
1.如果try塊中所有語句正常執行完畢,那么finally塊的居于就會被執行,這時分為以下兩種情況:
2如果finally塊執行順利,那么整個try-catch-finally程序塊正常完成。
2如果finally塊由于原因R突然中止,那么try-catch-finally程序塊的結局是“由于原因R突然中止(completes abruptly)”
2.如果try語句塊在執行過程中碰到異常V,這時又分為兩種情況進行處理:
2如果異常V能夠被與try相應的catch塊catch到,那么第一個catch到這個異常的catch塊(也是離try最近的一個與異常V匹配的catch塊)將被執行;這時就會有兩種執行結果:
2如果catch塊執行正常,那么finally塊將會被執行,這時分為兩種情況:
2如果finally塊執行順利,那么整個try-catch-finally程序塊正常完成。
2如果finally塊由于原因R突然中止,那么try-catch-finally程序塊的結局是“由于原因R突然中止(completes abruptly)”
2如果catch塊由于原因R突然中止,那么finally模塊將被執行,分為兩種情況:
2如果如果finally塊執行順利,那么整個try-catch-finally程序塊的結局是“由于原因R突然中止(completes abruptly)”。
2如果finally塊由于原因S突然中止,那么整個try-catch-finally程序塊的結局是“由于原因S突然中止(completes abruptly)”,原因R將被拋棄。
(注意,這里就正好和我們的例子相符合,雖然我們在testEx2中使用throw e拋出了異常,但是由于testEx2中有finally塊,而finally塊的執行結果是complete abruptly的(別小看這個用得最多的return,它也是一種導致complete abruptly的原因之一啊——后文中有關于),所以整個try-catch-finally程序塊的結果是“complete abruptly”,所以在testEx1中調用testEx2時是捕捉不到testEx1中拋出的那個異常的,而只能將finally中的return結果獲取到。
如果在你的代碼中期望通過捕捉被調用的下級函數的異常來給定返回值,那么一定要注意你所調用的下級函數中的finally語句,它有可能會使你throw出來的異常并不能真正被上級調用函數可見的。當然這種情況是可以避免的,以testEx2為例:如果你一定要使用finally而且又要將catch中throw的e在testEx1中被捕獲到,那么你去掉testEx2中的finally中的return就可以了。
這個事情已經在OMC2.0的MIB中出現過啦:服務器的異常不能完全被反饋到客戶端。)
2如果異常V沒有catch塊與之匹配,那么finally模塊將被執行,分為兩種情況:
2如果finally塊執行順利,那么整個try-catch-finally程序塊的結局就是“由于拋出異常V而突然中止(completes abruptly)”。
2如果finally塊由于原因S突然中止,那么整個try-catch-finally程序塊的結局是“由于原因S突然中止(completes abruptly)”,異常V將被拋棄。
3.如果try由于其他原因R突然中止(completes abruptly),那么finally塊被執行,分為兩種情況:
2如果finally塊執行順利,那么整個try-catch-finally程序塊的結局是“由于原因R突然中止(completes abruptly)”。
2如果finally塊由于原因S突然中止,那么整個try-catch-finally程序塊的結局是“由于原因S突然中止(completes abruptly)”,原因R將被拋棄。
3.4try-catch-finally程序塊中的return
從上面的一節中可以看出無論try或catch中發生了什么情況,finally都是會被執行的,那么寫在try或者catch中的return語句也就不會真正的從該函數中跳出了,它的作用在這種情況下就變成了將控制權(語句流程)轉到finally塊中;這種情況下一定要注意返回值的處理。
例如,在try或者catch中return false了,而在finally中又return true,那么這種情況下不要期待你的try或者catch中的return false的返回值false被上級調用函數獲取到,上級調用函數能夠獲取到的只是finally中的返回值,因為try或者catch中的return語句只是轉移控制權的作用。
3.5如何拋出異常
如果你知道你寫的某個函數有可能拋出異常,而你又不想在這個函數中對異常進行處理,只是想把它拋出去讓調用這個函數的上級調用函數進行處理,那么有兩種方式可供選擇:
第一種方式:直接在函數頭中throws SomeException,函數體中不需要try/catch。比如將最開始的例子中的testEx2改為下面的方式,那么testEx1就能捕捉到testEx2拋出的異常了。
boolean testEx2() throws Exception{
boolean ret = true;
int b=12;
int c;
for (int i=2;i>=-2;i--){
c=b/i;
System.out.println("i="+i);
}
return true;
}
第二種方式:使用try/catch,在catch中進行一定的處理之后(如果有必要的話)拋出某種異常。例如上面的testEx2改為下面的方式,testEx1也能捕獲到它拋出的異常:
boolean testEx2() throws Exception{
boolean ret = true;
try{
int b=12;
int c;
for (int i=2;i>=-2;i--){
c=b/i;
System.out.println("i="+i);
}
return true;
}catch (Exception e){
System.out.println("testEx2, catch exception");
Throw e;
}
}
第三種方法:使用try/catch/finally,在catch中進行一定的處理之后(如果有必要的話)拋出某種異常。例如上面的testEx2改為下面的方式,testEx1也能捕獲到它拋出的異常:
boolean testEx2() throws Exception{
boolean ret = true;
try{
int b=12;
int c;
for (int i=2;i>=-2;i--){
c=b/i;
System.out.println("i="+i);
throw new Exception("aaa");
}
return true;
}catch (java.lang.ArithmeticException e){
System.out.println("testEx2, catch exception");
ret = false;
throw new Exception("aaa");
}finally{
System.out.println("testEx2, finally; return value="+ret);
}
}
4關于abrupt completion
前面提到了complete abruptly(暫且理解為“突然中止”或者“異常結束”吧),它主要包含了兩種大的情形:abrupt completion of expressions and statements,下面就分兩種情況進行解釋。
4.1Normal and Abrupt Completion of Evaluation
每一個表達式(expression)都有一種使得其包含的計算得以一步步進行的正常模式,如果每一步計算都被執行且沒有異常拋出,那么就稱這個表達式“正常結束(complete normally)”;如果這個表達式的計算拋出了異常,就稱為“異常結束(complete abruptly)”。異常結束通常有一個相關聯的原因(associated reason),通常也就是拋出一個異常V。
與表達式、操作符相關的運行期異常有:
2A class instance creation expression, array creation expression , or string concatenation operatior expression throws an OutOfMemoryError if there is insufficient memory available.
2An array creation expression throws a NegativeArraySizeException if the value of any dimension expression is less than zero.
2A field access throws a NullPointerException if the value of the object referenceexpression is null.
2A method invocation expression that invokes an instance method throws a NullPointerException if the target reference is null.
2An array access throws a NullPointerException if the value of the array referenceexpression is null.
2An array access throws an ArrayIndexOutOfBoundsException if the value of the array index expression is negative or greater than or equal to the length of the array.
2A cast throws a ClassCastException if a cast is found to be impermissible at run time.
2An integer division or integer remainder operator throws an ArithmeticException if the value of the right-hand operand expression is zero.
2An assignment to an array component of reference type throws an ArrayStoreException when the value to be assigned is not compatible with the component type of the array.
4.2Normal and Abrupt Completion of Statements
正常情況我們就不多說了,在這里主要是列出了abrupt completion的幾種情況:
2break, continue, and return語句將導致控制權的轉換,從而使得statements不能正常地、完整地執行。
2某些表達式的計算也可能從java虛擬機拋出異常,這些表達式在上一小節中已經總結過了;一個顯式的的throw語句也將導致異常的拋出。拋出異常也是導致控制權的轉換的原因(或者說是阻止statement正常結束的原因)。
如果上述事件發生了,那么這些statement就有可能使得其正常情況下應該都執行的語句不能完全被執行到,那么這些statement也就是被稱為是complete abruptly.
導致abrupt completion的幾種原因:
2A break with no label
2A break with a given label
2A continue with no label
2A continue with a given label
2A return with no value
2A return with a given value A
2throw with a given value, including exceptions thrown by the Java virtual machine
5關于我們的編程的一點建議
弄清楚try-catch-finally的執行情況后我們才能正確使用它。
如果我們使用的是try-catch-finally語句塊,而我們又需要保證有異常時能夠拋出異常,那么在finally語句中就不要使用return語句了(finally語句塊的最重要的作用應該是釋放申請的資源),因為finally中的return語句會導致我們的throw e被拋棄,在這個try-catch-finally的外面將只能看到finally中的返回值(除非在finally中拋出異常)。(我們需要記住:不僅throw語句是abrupt completion的原因,return、break、continue等這些看起來很正常的語句也是導致abrupt completion的原因。)
1引言在JAVA語言出現以前,傳統的異常處理方式多采用返回值來標識程序出現的異常情況,這種方式雖然為程序員所熟悉,但卻有多個壞處。首先,一個API可以返回任意的返回值,而這些返回值本身并不能解釋該返回值是否代表一個異常情況發生了和該異常的具體情況,需要調用API的程序自己判斷并解釋返回值的含義。其次,并沒有一種機制來保證異常情況一定會得到處理,調用程序可以簡單的忽略該返回值,需要調用API的程序員記住去檢測返回值并處理異常情況。這種方式還讓程序代碼變得晦澀冗長,當進行IO操作等容易出現異常情況的處理時,你會發現代碼的很大部分用于處理異常情況的switch分支,程序代碼的可讀性變得很差。上面提到的問題,JAVA的異常處理機制提供了很好的解決方案。通過拋出JDK預定義或者自定義的異常,能夠表明程序中出現了什么樣的異常情況;而且JAVA的語言機制保證了異常一定會得到恰當的處理;合理的使用異常處理機制,會讓程序代碼清晰易懂。2?JAVA異常的處理機制當程序中拋出一個異常后,程序從程序中導致異常的代碼處跳出,java虛擬機檢測尋找和try關鍵字匹配的處理該異常的catch塊,如果找到,將控制權交到catch塊中的代碼,然后繼續往下執行程序,try塊中發生異常的代碼不會被重新執行。如果沒有找到處理該異常的catch塊,在所有的finally塊代碼被執行和當前線程的所屬的ThreadGroup的uncaughtException方法被調用后,遇到異常的當前線程被中止。3?JAVA異常的類層次JAVA異常的類層次如下圖所示:圖1 JAVA異常的類層次Throwable是所有異常的基類,程序中一般不會直接拋出Throwable對象,Exception和Error是Throwable的子類,Exception下面又有RuntimeException和一般的Exception兩類。可以把JAVA異常分為三類:第一類是Error,Error表示程序在運行期間出現了十分嚴重、不可恢復的錯誤,在這種情況下應用程序只能中止運行,例如JAVA虛擬機出現錯誤。Error是一種unchecked Exception,編譯器不會檢查Error是否被處理,在程序中不用捕獲Error類型的異常;一般情況下,在程序中也不應該拋出Error類型的異常。第二類是RuntimeException, RuntimeException是一種unchecked Exception,即表示編譯器不會檢查程序是否對RuntimeException作了處理,在程序中不必捕獲RuntimException類型的異常,也不必在方法體聲明拋出RuntimeException類。RuntimeException發生的時候,表示程序中出現了編程錯誤,所以應該找出錯誤修改程序,而不是去捕獲RuntimeException。第三類是一般的checked Exception,這也是在編程中使用最多的Exception,所有繼承自Exception并且不是RuntimeException的異常都是checked Exception,如圖1中的IOException和ClassNotFoundException。JAVA語言規定必須對checked Exception作處理,編譯器會對此作檢查,要么在方法體中聲明拋出checked Exception,要么使用catch語句捕獲checked Exception進行處理,不然不能通過編譯。checked Exception用于以下的語義環境:
(1)該異常發生后是可以被恢復的,如一個Internet連接發生異常被中止后,可以重新連接再進行后續操作。(2)程序依賴于不可靠的外部條件,該依賴條件可能出錯,如系統IO。(3)該異常發生后并不會導致程序處理錯誤,進行一些處理后可以繼續后續操作。
4?JAVA異常處理中的注意事項合理使用JAVA異常機制可以使程序健壯而清晰,但不幸的是,JAVA異常處理機制常常被錯誤的使用,下面就是一些關于Exception的注意事項:
1.不要忽略checked Exception請看下面的代碼:try
{
method1();? //method1拋出ExceptionA
}
catch(ExceptionA e)
{
e.printStackTrace();
}上面的代碼似乎沒有什么問題,捕獲異常后將異常打印,然后繼續執行。事實上在catch塊中對發生的異常情況并沒有作任何處理(打印異常不能是算是處理異常,因為在程序交付運行后調試信息就沒有什么用處了)。這樣程序雖然能夠繼續執行,但是由于這里的操作已經發生異常,將會導致以后的操作并不能按照預期的情況發展下去,可能導致兩個結果:一是由于這里的異常導致在程序中別的地方拋出一個異常,這種情況會使程序員在調試時感到迷惑,因為新的異常拋出的地方并不是程序真正發生問題的地方,也不是發生問題的真正原因;另外一個是程序繼續運行,并得出一個錯誤的輸出結果,這種問題更加難以捕捉,因為很可能把它當成一個正確的輸出。那么應該如何處理呢,這里有四個選擇:
(1)處理異常,進行修復以讓程序繼續執行。(2)重新拋出異常,在對異常進行分析后發現這里不能處理它,那么重新拋出異常,讓調用者處理。(3)將異常轉換為用戶可以理解的自定義異常再拋出,這時應該注意不要丟失原始異常信息(見5)。(4)不要捕獲異常。
因此,當捕獲一個unchecked Exception的時候,必須對異常進行處理;如果認為不必要在這里作處理,就不要捕獲該異常,在方法體中聲明方法拋出異常,由上層調用者來處理該異常。
2.不要一次捕獲所有的異常請看下面的代碼:try
{
method1();? //method1拋出ExceptionA
method2();? //method1拋出ExceptionB
method3();? //method1拋出ExceptionC
}
catch(Exception e)
{
……
}這是一個很誘人的方案,代碼中使用一個catch子句捕獲了所有異常,看上去完美而且簡潔,事實上很多代碼也是這樣寫的。但這里有兩個潛在的缺陷,一是針對try塊中拋出的每種Exception,很可能需要不同的處理和恢復措施,而由于這里只有一個catch塊,分別處理就不能實現。二是try塊中還可能拋出RuntimeException,代碼中捕獲了所有可能拋出的RuntimeException而沒有作任何處理,掩蓋了編程的錯誤,會導致程序難以調試。下面是改正后的正確代碼:try
{
method1();? //method1拋出ExceptionA
method2();? //method1拋出ExceptionB
method3();? //method1拋出ExceptionC
}
catch(ExceptionA e)
{
……
}
catch(ExceptionB e)
{
……
}
catch(ExceptionC e)
{
……
}
3.使用finally塊釋放資源finally關鍵字保證無論程序使用任何方式離開try塊,finally中的語句都會被執行。在以下三種情況下會進入finally塊:(1)try塊中的代碼正常執行完畢。(2)在try塊中拋出異常。(3)在try塊中執行return、break、continue。因此,當你需要一個地方來執行在任何情況下都必須執行的代碼時,就可以將這些代碼放入finally塊中。當你的程序中使用了外界資源,如數據庫連接,文件等,必須將釋放這些資源的代碼寫入finally塊中。必須注意的是,在finally塊中不能拋出異常。JAVA異常處理機制保證無論在任何情況下必須先執行finally塊然后在離開try塊,因此在try塊中發生異常的時候,JAVA虛擬機先轉到finally塊執行finally塊中的代碼,finally塊執行完畢后,再向外拋出異常。如果在finally塊中拋出異常,try塊捕捉的異常就不能拋出,外部捕捉到的異常就是finally塊中的異常信息,而try塊中發生的真正的異常堆棧信息則丟失了。請看下面的代碼:
Connection? con = null;
try
{
con = dataSource.getConnection();
……
}
catch(SQLException e)
{
……
throw e;//進行一些處理后再將數據庫異常拋出給調用者處理}
finally
{
try
{
con.close();
}
catch(SQLException e)
{
e.printStackTrace();
……
}
}運行程序后,調用者得到的信息如下java.lang.NullPointerException
at myPackage.MyClass.method1(methodl.java:266)而不是我們期望得到的數據庫異常。這是因為這里的con是null的關系,在finally語句中拋出了NullPointerException,在finally塊中增加對con是否為null的判斷可以避免產生這種情況。
4.異常不能影響對象的狀態異常產生后不能影響對象的狀態,這是異常處理中的一條重要規則。在一個函數中發生異常后,對象的狀態應該和調用這個函數之前保持一致,以確保對象處于正確的狀態中。如果對象是不可變對象(不可變對象指調用構造函數創建后就不能改變的對象,即創建后沒有任何方法可以改變對象的狀態),那么異常發生后對象狀態肯定不會改變。如果是可變對象,必須在編程中注意保證異常不會影響對象狀態。有三個方法可以達到這個目的:(1)將可能產生異常的代碼和改變對象狀態的代碼分開,先執行可能產生異常的代碼,如果產生異常,就不執行改變對象狀態的代碼。(2)對不容易分離產生異常代碼和改變對象狀態代碼的方法,定義一個recover方法,在異常產生后調用recover方法修復被改變的類變量,恢復方法調用前的類狀態。(3)在方法中使用對象的拷貝,這樣當異常發生后,被影響的只是拷貝,對象本身不會受到影響。
5.丟失的異常請看下面的代碼:public void method2()
{
try
{
……
method1();? //method1進行了數據庫操作}
catch(SQLException e)
{
……
throw new MyException(“發生了數據庫異常:”+e.getMessage);
}
}
public void method3()
{
try
{
method2();
}
catch(MyException e)
{
e.printStackTrace();
……
}
}上面method2的代碼中,try塊捕獲method1拋出的數據庫異常SQLException后,拋出了新的自定義異常MyException。這段代碼是否并沒有什么問題,但看一下控制臺的輸出:MyException:發生了數據庫異常:對象名稱'MyTable'無效。at MyClass.method2(MyClass.java:232)
at MyClass.method3(MyClass.java:255)原始異常SQLException的信息丟失了,這里只能看到method2里面定義的MyException的堆棧情況;而method1中發生的數據庫異常的堆棧則看不到,如何排錯呢,只有在method1的代碼行中一行行去尋找數據庫操作語句了,祈禱method1的方法體短一些吧。JDK的開發者們也意識到了這個情況,在JDK1.4.1中,Throwable類增加了兩個構造方法,public Throwable(Throwable cause)和public Throwable(String message,Throwable cause),在構造函數中傳入的原始異常堆棧信息將會在printStackTrace方法中打印出來。但對于還在使用JDK1.3的程序員,就只能自己實現打印原始異常堆棧信息的功能了。實現過程也很簡單,只需要在自定義的異常類中增加一個原始異常字段,在構造函數中傳入原始異常,然后重載printStackTrace方法,首先調用類中保存的原始異常的printStackTrace方法,然后再調用super.printStackTrace方法就可以打印出原始異常信息了。可以這樣定義前面代碼中出現的MyException類:public class MyExceptionextends Exception
{
//構造函數public SMException(Throwable cause)
{
this.cause_ = cause;
}
public MyException(String s,Throwable cause)
{
super(s);
this.cause_ = cause;
}
//重載printStackTrace方法,打印出原始異常堆棧信息public void printStackTrace()
{
if (cause_ != null)
{
cause_.printStackTrace();
}
super.printStackTrace(s);
}
public void printStackTrace(PrintStream s)
{
if (cause_ != null)
{
cause_.printStackTrace(s);
}
super.printStackTrace(s);
}
public void printStackTrace(PrintWriter s)
{
if (cause_ != null)
{
cause_.printStackTrace(s);
}
super.printStackTrace(s);
}
//原始異常private Throwable cause_;
}
6.不要使用同時使用異常機制和返回值來進行異常處理下面是我們項目中的一段代碼try
{
doSomething();
}
catch(MyException e)
{
if(e.getErrcode == -1)
{
……
}
if(e.getErrcode == -2)
{
……
}
……
}假如在過一段時間后來看這段代碼,你能弄明白是什么意思嗎?混合使用JAVA異常處理機制和返回值使程序的異常處理部分變得“丑陋不堪”,并難以理解。如果有多種不同的異常情況,就定義多種不同的異常,而不要像上面代碼那樣綜合使用Exception和返回值。修改后的正確代碼如下:try
{
doSomething();? //拋出MyExceptionA和MyExceptionB
}
catch(MyExceptionA e)
{
……
}
catch(MyExceptionB e)
{
……
}
7.不要讓try塊過于龐大出于省事的目的,很多人習慣于用一個龐大的try塊包含所有可能產生異常的代碼,這樣有兩個壞處:閱讀代碼的時候,在try塊冗長的代碼中,不容易知道到底是哪些代碼會拋出哪些異常,不利于代碼維護。使用try捕獲異常是以程序執行效率為代價的,將不需要捕獲異常的代碼包含在try塊中,影響了代碼執行的效率。
總結
以上是生活随笔為你收集整理的java 异常 理解_java异常理解(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 30万、2.0T以下购置税减半!多家车企
- 下一篇: 微信这波更新 帮你立省20GB磁盘空间!