一、程序的錯誤類型
在程序設計中,無論規模是大是小,錯誤總是難免的。程序的設計很少有能夠一次完成,沒有錯誤的(不是指HelloWorld這樣的程序,而是要實現一定的功能,具備一定實用價值的程序),在編程的過程中由於種種原因,總會出現這樣或那樣的錯誤,這些程序的錯誤就是我們常說的“Bug”,而檢測並修正這些錯誤的方法就是“Debug”(調試)。
基本上所有的集成開發環境都提供了強大的和程序調試功能,在程序進行編譯,連接,運行時,會對程序中錯誤進行診斷。
程序的錯誤可以抽象分為三類:語法錯誤、運行錯誤和邏輯錯誤。
1、語法錯誤
是指由於編程中輸入不符合語法規則而產生的。程序編譯就通不過,程序不能運行起來。此類錯誤最簡單,調試起來比較容易
例如:表達式不完整、缺少必要的標點符號、關鍵字輸入錯誤、數據類型不匹配、循環語句或選擇語句的關鍵字不匹配等。通常,編譯器對程序進行編譯的過程中,會把檢測到的語法錯誤以提示的方式列舉出來,又稱為編譯錯誤。
語法錯誤的調試,則可以由集成開發環境提供的調試功能來實現,在程序進行編譯時,編譯器會對程序中的語法錯誤進行診斷。
編譯診斷的語法錯誤分為3中:致命錯誤、錯誤和警告。
(1)致命錯誤:這個錯誤大多是編譯程序內部發生的錯誤,發生這類錯誤時,編譯被迫中止,只能重新啟動編譯程序,但是這類錯誤很少發生,為了安全,編譯前最好還是先保存程序。
(2)錯誤:這個錯誤通常是在編譯時,語法不當所引起的。例如:括號不匹配,變量未聲明等。產生這類錯誤時,編譯程序會出現報錯提示,我們根據提示對源程序進行修改即可。這類錯誤是出現最多的。
(3)警告:是指被編譯程序懷疑有錯,但是不確定,有時可強行通過。例如:沒有加void聲明的主函數沒有返回值,double數據被轉換為float類型等。這些警告中有些會導致錯誤,有些可以通過。
常規解決方法:此類錯誤一般程序編譯系統會自動提示相應的錯誤地點和錯誤原因,比如哪一行代碼少了個括號等諸如此類的提示,常見的錯誤,看懂直接改正即可,如果是看不懂原因,可以將錯誤提示信息輸入搜索引擎查找一下,一般都能找到具體的解決辦法。或者有些編程平台會本身提供一個本地或者在線的信息庫,提供詳細的錯誤原因和解決辦法,比如微軟的.NET開發平台。
2、運行錯誤
指程序在運行過程中出現的錯誤。程序通過語法錯誤檢測,但是運行的時候出現錯誤,導致程序被迫終止,此類錯誤有特定的發生條件,因此能夠准確的定位錯誤代碼段,因而調試也比較方便。
例如:除法運算時除數為0 、數組下標越界、文件打不開、磁盤空間不夠、數據庫連接錯誤等。
此類錯誤發生時,編譯平台一般也會提示相應的信息,對於常規的錯誤會有比較精確地提示,但有時提示的錯誤原因會比較模糊,但因為此類錯誤一般在程序運行時,只在特定的條件下才會發生,所以根據錯誤發生的條件,能夠大致判斷程序出錯的代碼段,結合錯誤的原因,也能比較方便的調試出錯誤。
3、邏輯錯誤
程序運行后,沒有得到設計者預期的結果,這就說明程序存在邏輯錯誤。這種錯誤在語法上是有效的,但是在邏輯上是錯誤的。
程序運行了,也沒有出錯,但是執行出來的結果不是用戶想要的,分為兩種情況:
A、 能夠看出錯誤:比如查詢工資大於5000的人員名單,卻出現了3000的;
B、 看不出錯誤,直到因緣際會發現程序肯定出錯了,后果很嚴重:比如進行一個符合大型運算,把某個常數輸入錯了,最后的結果人工無法判斷對錯,又以該結果進行其它的運算等等,最后發現錯了誤差過大,就得從頭排查錯誤。
例如:使用了不正確的變量,指令的次序錯誤,循環的條件不正確,程序設計的算法考慮不周全等。通常,邏輯錯誤也會附帶產生運行錯誤。在一般情況下,編譯器在編譯程序時,不能檢測到程序中的邏輯錯誤,也不會產生邏輯錯誤的提示,因此邏輯錯誤比較難排除,需要程序員仔細的分析程序,並借助集成開發環境提供的調試工具,才能找到出錯的原因,並排除錯誤。
二、java的異常處理(錯誤處理)
程序的錯誤就是通常的異常,也叫Exception。
對於語法錯誤,java編譯系統在編就能發現檢查出錯誤。
對於邏輯錯誤,編譯系統是無法發現錯誤的,錯誤需要人為去發現排除錯誤。
對於運行錯誤,Java語言中代表異常時,使用一個專門的類來代表一種特定的異常情況,在系統中傳遞的異常情況就是該類的對象,所有代表異常的類組成的體系就是Java語言中的異常類體系。
1、java異常類
Java的異常是一個對象,所有的異常都直接或間接地繼承Throwable類。Throwable類的繼承層次結構如下:
為了方便對於這些可傳遞對象的管理,Java API中專門設計了java.lang.Throwable類,只有該類子類的對象才可以在系統的異常傳遞體系中進行。該類的兩個子類分別是:
1)Error類
該類代表錯誤,指程序無法恢復的異常情況。對於所有錯誤類型以及其子類,都不要求程序進行處理。常見的Error類例如內存溢出StackOverflowError等。
2)Exception類
該類代表異常,指程序有可能恢復的異常情況。該類就是整個Java語言異常類體系中的父類。使用該類,可以代表所有異常的情況。
在Java API中,聲明了幾百個Exception的子類分別來代表各種各樣的常見異常情況,這些類根據需要代表的情況位於不同的包中,這些類的類名均以 Exception作為類名的后綴。如果遇到的異常情況,Java API中沒有對應的異常類進行代表,也可以聲明新的異常類來代表特定的情況。
在這些異常類中,根據是否是程序自身導致的異常,將所有的異常類分為兩種:
a) RuntimeException及其所有子類
該類異常屬於程序運行時異常,也就是由於程序自身的問題導致產生的異常,例如數組下標越界異常ArrayIndexOutOfBoundsException等。
該類異常在語法上不強制程序員必須處理,即使不處理這樣的異常也不會出現語法錯誤。
b) 其它Exception子類
該類異常屬於程序外部的問題引起的異常,也就是由於程序運行時某些外部問題導致產生的異常,例如文件不存在異常FileNotFoundException等。
該類異常在語法上強制程序員必須進行處理,如果不進行處理則會出現語法錯誤。
熟悉異常類的分類,將有助於后續語法中的處理,也使得在使用異常類時可以選擇恰當的異常類類型。
2、常見的error類
異常類名 |
用途 |
LinkageError |
動態鏈接失敗 |
VirtualMachineError |
虛擬機錯誤 |
AWTError |
AWT錯誤 |
3、常見運行時異常類
異常類名 |
用途 |
ArithmeticException |
數學運算異常,比如除數為零的異常 |
IndexOutOfBoundsException |
下標越界異常,比如集合、數組等 |
ArrayIndexOutOfBoundsException |
訪問數組元素的下標越界異常 |
StringIndexOutOfBoundsException |
字符串下標越界異常 |
ClassCaseException |
類強制轉換異常 |
NullpointerException |
當程序試圖訪問一個空數組中的元素,或訪問一個空對象中的方法或變量時產生的異常。 |
4、常用的非運行時異常
異常類名 |
用途 |
ClassNotFoundException |
指定類或接口不存在的異常 |
IllegalAccessException |
非法訪問異常 |
Ioexception |
輸入輸出異常 |
FileNotFoundException |
找不到指定文件的異常 |
ProtocolException |
網絡協議異常 |
SocketException |
Socket操作異常 |
MalformedURLException |
統一資源定位符(URL)的格式不正確的異常 |
5、Java的異常處理機制描述如下:
在一個方法的運行過程中,如果發生了異常,
則這個方法(或者是Java虛擬機)生成一個代表該異常的對象(它包含了異常的詳細信息),並把它交給運行時系統,運行時系統尋找相應的代碼來處理這一異常。我們把生成異常對象並把它提交給運行時系統的過程稱為拋出(throw)一個異常。
運行時系統尋找相應的代碼來處理這一異常,系統在方法的調用棧中查找,從產生異常的方法開始進行回朔,沿着被調用的順序往前尋找,直到找到包含相應異常處理的方法為止。其過程如圖10-1所示。這一過程稱為捕獲(catch)一個異常。
如該異常未進行成功捕獲,則程序將終止運行。
5、異常捕獲和處理
格式:
try{
正常程序段,可能拋出異常;
}
catch (異常類1 異常變量) {
捕捉異常類1有關的處理程序段;
}
catch (異常類2 異常變量) {
捕捉異常類2有關的處理程序段;
}
……
finally{
一定會運行的程序代碼;
}
l try塊——捕獲異常:用於監控可能發生異常的程序代碼塊是否發生異常,如果發生異常,Try代碼塊將拋出異常類所產生的對象並立刻結束執行,而轉向異常處理catch部分。
對於系統產生的異常或程序塊中未用try監控所產生的一場,將一律由java 編譯系統自動將異常對象拋出。
l catch塊——處理異常 :拋出的異常對象如果屬於catch內所定義的異常類,則catch會捕獲該異常,並進入catch中的對應代碼段繼續運行程序,如果異常對象不屬於catch中所定義的異常類,則進入finally塊繼續運行程序。
Catch包括兩個參數:一個是類名,指出捕獲的異常類型,必須使Throwable類的子類;一個是參數名,用來引用被捕獲的對象。Catch塊所捕獲的對象並不需要與它的參數類型精確匹配,它可以捕獲參數中指出的異常類的對象及其所有子類的對象
l finally塊——最終處理:無論是否發生異常都會執行的語句塊。比如執行關閉打開的文件、刪除臨時文件,關閉數據庫連接等操作。
注意:
l 一個try、catch、finally塊之間不能插入任何其它代碼
l catch可以有多個,try和finally只能有一個
l try后面必須要跟catch、finally其中的一個,即但一個try、catch、finally語句只能省略catch、finally中的一個。
定義多個catch可精確地定位java異常。如果為子類的異常定義了特殊的catch塊,而父類的異常則放在另外一個catch塊中,此時,必須滿足以下規則:子類異常的處理塊必須在父類異常處理塊的前面,否則會發生編譯錯誤。所以,越特殊的異常越在前面處理,越普遍的異常越在后面處理。這類似於 制訂防火牆的規則次序:較特殊的規則在前,較普通的規則在后。
異常類常用方法
常用非法 |
用途 |
Void String getMessage() |
返回異常對象的一個簡短描述 |
Void String toString() |
獲取異常對象的詳細信息 |
Void printStackTrace() |
在控制台上打印異常對象和它的追蹤信息 |
6、異常實例
1) 數學運算異常
class MathException{
public static void main(String args[]){
inta=5,b=0;
intc=a/b; //除數為0,出現異常
System.out.print(c);
}
}
在命令提示符下運行該程序,可以發現編譯正常,但是執行時出現錯誤的提示如下:
Exception inthread "main" java.lang.ArithmeticException: / by zero
at MathException.main(MathException.java:4)
翻譯過來就是:
在類java.lang.ArithmeticException主線程中“main”方法中出現異常:除數為零,(MathException.java:4“此處指MathException類中的第四行”)
這是一個典型的運行錯誤,程序告訴了一下幾個信息;
l 出錯的異常類:java.lang.ArithmeticException
l 出錯的類:MathException
l 出錯的代碼:MathException.java:4
因為編譯系統給出了出錯的原因和出錯類的位置,可以方便地進行代碼調試。
2)捕獲數學運算異常的處理
public classTryCatchDemo{ public static void main(String args[]){ try { int a=8,b=0; int c=a/b; System.out.print(c); } // ArithmeticException是異常類的名稱,e是引用的參數名稱 catch(ArithmeticException e) { System.out.println("發生的異常簡短描述是:"+e.getMessage()); System.out.println("發生的異常詳細信息是:"+e.toString()); } } }
程序執行結果:
發生的異常簡短描述是:/ by zero
發生的異常詳細信息是:java.lang.ArithmeticException: / by zero
3)數組下標越界異常
public class arrayException{ publicstatic void main(String[] args) { //被監視的代碼塊 try{ int[]a=new int[4]; a[4]=9; } //處理下標越界異常 catch(ArrayIndexOutOfBoundsExceptionaiobe){ System.out.println("這里出現的錯誤類型是:數組下標越界!!"); } //處理空引用異常 catch(NullPointerExceptionnpe) { System.out.println("這里出現的錯誤類型是:空引用!!"); } finally{ System.out.println("程序無條件執行該語句!"); } } }
三、異常的拋出
異常的拋出可以分為兩大類:
l 系統自動拋出異常
比如上面的例子就是系統自動拋出異常,通過try catch捕獲異常對象,並繼續相應的處理。
l 通過關鍵字throw將異常對象顯性地拋出。
即在程序中生成自己的異常對象,即異常可以不是出錯產生,而是人為編寫代碼主動拋出。顯性拋出異常從某種程度上實現了將處理異常的代碼從正常流程代碼中分離開了,使得程序的主線保證相對完整,同時增加了程序的可讀性和可維護性。異常沿着調用層次向上拋出,交由調用它的方法來處理。
為什么要在方法中拋出異常?
系統自動拋出異常一般就能解決大部分問題,但有時候,程序會產生特定的要求,需要由用戶自己定義異常信息,又或者聯合開發程序模塊時,不同程序員需要將各自負責代碼部分盡量避免因程序出錯影響其他人的編碼,都需要顯式拋出異常,以便程序進行處理。這時候就需要在方法中拋出異常。
異常拋出的語法:
throw new 異常類( );
其中異常類必須Throwable類及其子類。
比如:
throw new ThrowableObject();
ArithmeticException e = new ArithmeticException();
throw e;
throws子句的方法聲明的一般格式如下:
<類型說明> 方法名(參數列表) throws <異常類型列表>
{
方法體;
}
舉例:
class ThrowException{ // throwOne方法后用throws聲明異常類ArithmeticException static void throwOne(int i) throwsArithmeticException { if(i==0) throw new ArithmeticException("i值為零"); //用throw拋出一個異常 } public static void main(String args[]){ //捕獲異常 try{ throwOne(0); } catch(ArithmeticException e){ System.out.println("已捕獲到異常錯誤: "+e.getMessage()); } } }
程序執行結果:
已捕獲到異常錯誤: i值為零
例:
import java.io.*; class Father{ //父類方法,沒有拋出異常 publicvoid myFunction() { System.out.println("這里是父類方法,該方法沒有異常拋出!!"); } } class Son extends Father{ //子類重寫父類方法,有捕獲異常拋出 publicvoid myFunction() throws InterruptedException { System.out.println("這里是子類方法,該方法拋出InterruptedException異常!!"); } } public class TestExceptionDemo{ publicstatic void main(String[] args) { //創建子類對象 Son s=new Son(); //受監視的代碼 try { s.myFunction(); } //異常處理代碼 catch(InterruptedExceptione) { e.printStackTrace(); } } }
例:重新拋出異常對象
程序執行時, 要求用戶從鍵盤輸入一個字符號。當輸入‘0’時,程序執行結果:devided by 0;當輸入非‘0’字符時,程序執行運算出的結果。
import java.io.*;
class JavaThrows{
public int compute(int x) throws ArithmeticException
{
int z = 10/x; //可能拋出異常類型ArithmeticException 的對象
return z;
}
public void method1()
{ intx;
try{
x=System.in.read(); //可能拋出異常類型IOException的對象;
x=x-48;
x=compute(x); //拋出異常類型ArithmeticException的對象
System.out.println(x);
}
catch(IOException ioe){ //捕獲異常類型IOException的對象;
System.out.println("readerror");
}
catch(ArithmeticException e){ //捕獲異常類型ArithmeticException的對象
System.out.println("devided by0");
}
}
public staticvoid main(String args[]) {
JavaThrowst1=new JavaThrows();
t1.method1();
}
}
四、自定義異常
用戶自定義的異常類,只需繼承一個已有的異常類就可以了,包括繼承Execption類及其子類,或者繼承已自定義好的異常類。如果沒有特別說明,可以直接用Execption類作為父類。
自定義類的格式如下:
class 異常類名 extends Exception
{
……
}
n 自定義異常類必須繼承自Throwable或Exception類,建議用Exception類。一般不把自定義異常作為Error的子類,因為Error通常被用來表示系統內部的嚴重故障。
n 當自定義異常是從RuntimeException及其子類繼承而來時,該自定義異常是運行時異常,程序中可以不捕獲和處理它。
n 當自定義異常是從Throwable、Exception及其子類繼承而來時,該自定義異常是編譯時異常,也即程序中必須捕獲並處理它。
使用自定義異常的步驟如下:
l 首先通過繼承java.lang.Exception類聲明自定義的異常類。
l 在方法的聲明部分用throws語句聲明該方法可能拋出的異常。
l 在方法體的適當位置創建自定義異常類的對象,並用throw語句將異常拋出。
l 調用該方法時對可能產生的異常進行捕獲,並處理異常。
例:自定義一個異常類,輸入一個數,大於10,捕獲異常。
// MyExceptionDemo.java classMyException extends Exception { //繼承了Exception這個父類 private int detail; MyException(int a) { detail = a; } public String toString() { return"MyException[" + detail + "]"; } } class MyExceptionDemo{ static void compute(int a) throwsMyException { System.out.println("調用 compute(" + a + ")"); if(a > 10){ //a大於10,則拋出異常 throw newMyException(a); } System.out.println("常規退出 "); } public static void main(String args[]){ try { compute(1); //a小於10,常規退出 compute(20); //a大於10,則拋出異常 } catch (MyException e) { System.out.println("捕捉 " + e); //這樣就可以用自己定義的類來捕捉異常了 } } }
例:計算兩個數之和,當任意一個數超出范圍(10,20)時,拋出自己的異常。
//NewException.java classNumberRangeException extends Exception{ public NumberRangeException(String msg){ super(msg); } //throws重新拋出異常NumberRangeException public int CalcAnswer(String str1,String str2) throws NumberRangeException{ int int1, int2; int answer = -1; try { int1 = Integer.parseInt(str1); //可能產生異常對象NumberFormatException e int2 = Integer.parseInt(str2); //可能產生異常對象NumberFormatException e if( (int1 < 10) || (int1 > 20) || (int2< 10) || (int2 > 20) ){ NumberRangeException e = new NumberRangeException("Numbersare not within the specified range."); throw e; //拋出自定義異常對象NumberRangeExceptione } answer = int1 + int2; } catch (NumberFormatExceptione){ //捕獲異常對象NumberRangeException e System.out.println( e.toString() ); } return answer; } //在調用方法getAnswer中捕獲異常 public void getAnswer(){ String answerStr; try{ //將num1、num2的中的數字更改為小於10或大於20,以查看捕獲異常結果。 Stringnum1="13"; Stringnum2="12"; int answer = CalcAnswer(num1, num2); //拋出異常對象NumberRangeException e answerStr = String.valueOf(answer); } catch (NumberRangeExceptione){ //捕獲異常對象NumberRangeException e answerStr = e.getMessage(); } System.out.println(answerStr); } public static void main(String args[]) { NumberRangeExceptiont1=new NumberRangeException("test"); t1.getAnswer(); } }