Java學習(四).異常處理
?
??? 任何一個軟件或程序都可能在運行的過程中出現故障,問題的關鍵是故障出現以后如何處理?誰來處理?怎樣處理?處理后系統能否恢復正常的運行?本章在介紹Java處理這類問題基本方法的基礎上,討論包含異常處理的Java程序的設計方法。
?
?
1、異常和異常處理的兩種方法
?
??? 異常是指程序在執行過程中出現的意外事件。異常通常會使程序的正常流程被打斷。例如,程序在向磁盤中讀寫文件時磁盤出現問題、算術運算中被除數為0、數組下標越界、輸入/輸出時文件不存在、輸入數據格式不正確、程序本身錯誤,等等。
??? 一般情況下,程序中需要處理異常情況,通過對異常情況的處理,可以使程序的執行流程繼續下去,并進行一些異常處理;否則,程序的正常執行流程會被中斷。
?
1、異常的基本類型
?
??? 異常處理的第一步是確定異常的類型,常見的異常情況一般分為以下幾類:
??? 用戶輸入錯誤:指用戶鍵盤輸入錯誤,輸入格式不對或輸入內容不符合程序要求等。例如,用戶界面要求輸入一個整數,而用戶輸入了一個字符串,或者要求用戶輸入一個URL地址,但用戶輸入的地址語法錯誤等。
??? 設備故障:計算機硬件設備有時也會出故障,例如打印機沒連接好,要求的網頁沒有找到等。
? ? 物理限制:物理設備本身的限制,例如硬盤已存滿,內存已用完等。
? ? 代碼錯誤:程序員編寫的代碼出現錯誤,例如從一個空的堆棧中彈出元素,數組下標為負數等。
?
2、if-else形式的異常處理方法
?
??? 用if-else語句(或switch-case語句)可以發現異常并做出相應處理。例如,在程序中要將一個文件整個讀入內存,通常的步驟是:
??? (1)打開文件。
??? (2)判斷文件的大小。
??? (3)分配內存。
??? (4)將文件讀入內存。
??? (5)關閉文件。
??? 在上述的過程中可能出現很多問題,例如:
??? (1)文件打不開。
??? (2)不能決定文件的大小
??? (3)內存不能分配
??? (4)讀入失敗
??? (5)文件不能關閉
??? 針對這些情況,需要在程序中做出相應的處理,若用if-else語句,則程序偽碼如下:
??? Class ReadFile
??? {
??? ? 初始化 錯誤代碼 = 0;
??? ? 打開文件;
??? if (文件可以打開)
??? {
????? 計算文件的長度;
????? if (文件的長度可以決定)
????? {
??????? 分配內存
??????? if (有足夠的內存)
??????? {
????????? 將文件讀入內存
????????? if (讀入失敗)
????????? {
??????????? 錯誤代碼 = "讀入文件時失敗"
???? ? ?? }
??????? }
??????? else
????????? {
??? ? ????? 錯誤代碼 = "內存不足"
????????? }
??????? }
??????? else
??????? {
????????? 錯誤代碼 = "不能決定文件的大小"
??????? }
????? 關閉文件;
?? ?? if (文件沒有關閉 && 錯誤代碼 == 0)
?? ?? {
???????? 錯誤代碼 = "文件關閉失敗"
????? }
??? }
??? else
??? {
?????? 錯誤代碼 = "文件打不開"
??? }
?? return錯誤代碼
}
???
用if-else語句方式可以發現異常并做出相應的處理。但是,程序員由于過多地分析程序中異常情況的發生情況,在程序中過多地使用if-else語句,會使程序員正常的程序設計思路受到影響。另外,程序中過多地使用if-else語句,也會影響程序的算法思想的可讀性。而且,會更為重要的是,有些異常情況是不可預見的。例如計算機的連接中斷,打印機紙張用完等。所以,if-else形式不是處理異常的好的方法.
?
3、
Java的異常處理方法
?
??? 在Java語言中,用try模塊和catch模塊把程序的正常流程代碼和異常處理代碼分離。
??? 對于上述例子,按照Java的異常處理方式,偽碼表示的程序代碼如下:
??? Class ReadFile
??? {
????? try
????? {
??????? 打開文件
??????? 計算文件的長度
???? ?? 分配內存
???? ?? 將文件讀入內存
???? ?? 關閉文件
????? }
????? catch (文件打開失敗異常)
????? {
??????? 處理文件打開失敗異常
????? }
????? catch (文件的長度不能確定異常)
????? {
????? 處理文件的長度不能確定異常
????? }
????? catch (分配內存失敗異常)
????? {
????? 處理分配內存失敗異常
????? }
????? catch (文件讀入內存失敗異常)
????? {
????? 處理文件讀入內存失敗異常
????? }
????? catch (關閉文件失敗異常)
????? {
????? 處理關閉文件失敗異常
????? }
????? }
??? 從上述偽碼可以看出,程序中可能出現的異常情況都放進了try模塊中,而對于各種異常情況,設計了相應的catch模塊,這些模塊可以用來捕捉這些異常情況,并進行相應的處理。
??? 在Java程序中,如果設計人員對可能出現的異常沒有設計相應的try模塊和catch模塊,或設計人員無法預見的異常情況,系統會將出現的異常交由Java虛擬機(JVM)處理,此時Java虛擬機會自動捕捉這些異常情況,并將異常情況在屏幕上顯示出來。
?
?
2、Java的異常類
?
??? Java語言對大多數常見的異常都定義了異常類。這些異常類可以分為兩大類:Error類和Exception類。
??? Error(錯誤)類和Exception (異常)類的區別在于:錯誤指的是系統異?;蜻\行環境出現的異常,這些異常一般是很嚴重的異常,即使捕捉到通常也無法處理,例如Java虛擬機異常。而Exception類的異常指的是一般的異常,例如,輸入/輸出(I/O)異常、數據庫訪問(SQL)異常等。對這些異常應用程序可以進行處理。Java的所有異常類都繼承自Throwable類。異常類是Java語言包(java.lang)中的類。
??? Java異常類的繼承關系如圖所示:
??? .01_thumb.jpg)
?
1、Error類及其子類
?
??? 當系統動態連接失敗,或者出現虛擬機的其他故障時,Java虛擬機會拋出Error錯誤。程序一般不用捕捉Error錯誤,由系統進行這類錯誤的捕捉和處理。Error類及其子類定義了系統中大多數這類錯誤。
??? 這里介紹Error類的兩個子類及其子類:LinkageError(結合錯誤)類及其子類和VirtualMachineErrror(虛擬機錯誤)類及其子類。
(1)LinkageError類及其子類
??? LinkageError(結合錯誤)類及其子類定義的是一個類依存于另一個類,但在編譯前者時后者出現了與前者不兼容情況的各種錯誤。
??? LinkageError類的子類有:
? ??? ClassFormatError???? ? 類格式錯誤
? ??? ClassCircularityError? 類循環錯誤
? ??? NoClassDefFoundError? 類定義無法找到錯誤
? ??? VerifyError????? ????? 校驗錯誤
? ??? AbstractMethodError??? 抽象方法錯誤
? ??? NoSuchFieldError???? ? 沒有成員變量錯誤
? ??? InstantiationError??? 實例錯誤
(2)VirtualMachineErrror類及其子類
??? VirtualMachineErrror(虛擬機錯誤)類及其子類定義的是系統在虛擬機損壞或需要運行程序的資源耗盡時出現的各種錯誤。
??? VirtualMachineErrror類的子類有:
? ??? InternalError?????? 內部錯誤
? ??? OutOfMemoryError??? 內存溢出錯誤
? ??? StackOverflowError? 堆棧溢出錯誤
? ??? UnkownError?????? ? 未知錯誤
?
2、Exception類及其子類
?
??? Exception類及其子類定義了程序中大多數可以處理的異常。
??? 這里介紹Exception類的兩個子類及其子類:RuntimeException (運行時異常)類及其子類和CheckedException (檢查異常) 類及其子類。
(1)RuntimeException類及其子類
??? RuntimeException(運行時異常)類及其子類定義的是Java程序執行過程中可能出現的各種異常,RuntimeException類的子類有:
? ??? ArithmeticException?????? ????? 算術運算異常
? ??? ArrayStoreException?????? ????? 數組存儲異常
? ??? ArrayIndexOutOfBoundsException? 數組下標越界異常
? ??? CaseCastException????? ??????? 類型轉換異常
? ??? IllegalArgumentException????? ? 非法參數異常
? ??? IllegalThreadStateException???? 非法線程狀態異常
? ??? NumberFormatException????? ???? 數字格式異常
? ??? IlegalMonitorStateException???? 非法監視狀態異常
? ??? IndexOutofBoundsException????? 下標超出范圍異常
? ??? NegativeArraySizeEXception???? 負數組個數異常
? ??? NullPointerException?????? ???? 空指針異常
? ??? SecurityException??????? ?????? 安全異常
? ??? EmptyStackException?????? ????? 空棧異常
? ??? NoSuchElementException????? ??? 沒有元素異常
(2)CheckedException 及其子類
??? CheckedException (檢查異常)類及其子類定義的是Java程序編譯時編譯器發現的各種異常。
??? CheckedException類的子類有:
? ??? ClassNotFoundException????? ?類找不到異常
? ??? CloneNotSupportedException?? 復制不支持異常
? ??? IllegalAccessException?????? 非法訪問異常
? ??? InstantiationException?????? 實例異常
? ??? InterruptedException????? ?? 中斷異常
? ??? IOException???????? ???????? 輸入/輸出異常
? ??? FileNotFoundException?????? 文件找不到異常
? ??? InterruptedIOException?????? 中斷輸入/輸出異常
?
3、Throwable類的方法
?
??? Error類和Exception類的方法基本都是從Throwable類中繼承來的。
??? Throwable類的構造方法主要有:
? ??? Throwable()????? ???????? //構造一個對象,其錯誤信息串為null
? ??? Throwable(String message) //構造一個對象,其錯誤信息串為message
??? Throwable類的方法主要有:
? ??? String getMessage()????? ?返回對象的錯誤信息
? ??? void printStackTrace()?? 輸出對象的跟蹤信息到標準錯誤輸出流
? ??? void printStackTrace(PrintStream s)?? 輸出對象的跟蹤信息到輸出流s
? ??? String toString()????? ?? 返回對象的簡短描述信息
?
4、異常類的對象
?
???
應用程序中一旦出現異常,系統會產生一個異常類的對象,下面討論的catch語句后面的參數將接收到這樣的一個對象。
??? 為了語言簡潔,通常把異常類的對象也簡稱異常。這就像第6章中我們也把組件類的對象簡稱組件。
?
?
3、Java的異常處理方法
?
??? 在Java程序中,用try-catch(或try-catch-finally)語句來拋出、捕捉和處理異常。
??? try-catch-finally語句的語法格式是:
??? try
??? {
??? ? 可能拋出異常的語句模塊;
??? }
?
??? catch (異常類型1)
??? {
????? 處理異常類型1語句;
??? }
??? ……
??? catch (異常類型n)
??? {
????? 處理異常類型n語句;
??? }
??? finally
??? {
????? 無論是否拋出異常都要執行的語句;
??? }
try-catch-finally語句的功能為:
(1)try模塊:
??? 所有可能拋出異常的語句都放入try模塊中。try模塊中的語句是程序正常流程要執行的語句,但是在執行過程中有可能出現異常。如果像前面程序設計例子那樣沒有使用try模塊,則除了系統定義的異常外,語句執行過程中出現的其他異常不會被拋出。
(2)catch模塊:
??? 主要負責對拋出的異常做相應的處理。所有拋出的異常都必須是Throwable類或其子類的對象。try模塊中的語句可能拋出很多不同類型的異常,所以需要針對不同類型的異常分別設計catch模塊。一般程序中會有若干個catch模塊,每一模塊處理一種類型的異常。
??? 對于這些catch模塊,其排列的先后順序有如下規定:出現在前面的catch模塊中的異常類一定要是后面的catch模塊中的異常類的子類, 即前面的catch模塊中的異常類對象一定比后面的catch模塊中的異常類對象特殊。這是因為catch模塊是按順序執行的。如果try模塊拋出異常,系統會按catch模塊的排列次序依次查找,如果前面catch模塊的異常類型匹配不上,就按順序和后面的catch模塊匹配。如果在所有的catch模塊中都匹配不上,就交給系統的虛擬機,由虛擬機來處理這個異常。
??? 程序中即使沒有try模塊,若出現了系統定義的異常,系統也會自動拋出,并由系統負責捕捉和處理;但是程序中若有了catch模塊,則一定要有try模塊。另外,若用戶希望按照自己的意圖處理程序中可能出現的系統定義的異常,也要在程序中使用try-catch語句,否則出現的異常將由系統捕捉和處理。
(3)finally模塊:
??? finally模塊中的語句是必須執行的語句。無論try模塊中是否拋出異常,finally模塊中的語句時都要被執行。這個模塊是可選的。如果設計了finally模塊,通常在這個模塊中做一些資源回收的工作。
?
【例8.1】try-catch-finally的執行過程示例。
要求:設計一個用命令行參數輸入數據的程序,若該參數的數值輸入的不正確會出現異常。
程序設計如下:
public class TryCatchSequence
{
?? public static void main(String args[])
?? {
????? int a,b,c;
????? try
????? {??
???????? a=100;
???????? b=Integer.parseInt(args[0]);??? ??????? //把命令行輸入的字符串轉換成整數
???????? c=a/b;
???????? System.out.println("c="+c);??????
???????? System.out.println("No exception.");?????????
????? }
????? catch(ArrayIndexOutOfBoundsException e)??? //數組下標越界異常
????? {
???????? System.out.println("Exception caught!");
???????? e.printStackTrace();
????? }
????? catch(ArithmeticException e)?????????????? //處理算術運算異常
????? {
???????? System.out.println("Exception caught!");
???????? e.printStackTrace();
????? }
????? finally
????? {
??? ???? System.out.println("Always executed");
????? }
?? }
}
我們分以下幾種情況分別運行上述程序:
(1) 沒有異常拋出。在當前工作目錄下鍵入如下命令(注意:該程序正常運行需要從命令行給出參數,且該參數值不能為0):
java TryCatchSequence 10
則程序的運行結果如下:
c=10
No exception.
Always executed
程序的運行結果說明:系統執行完了try模塊的所有程序, 由于程序運行時沒有異常拋出,所以系統跳過catch模塊, 最后執行了finally模塊。
(2) 有異常拋出。上述程序可能有如下兩種類型的異常發生。
--拋出數組下標越界異常。
在當前工作目錄下鍵入如下命令(注意:該程序運行時無法從命令行得到參數值):
java TryCatchSequence
程序運行結果如下:
Exception caught!
java.lang.ArrayIndexOutOfBoundsException: 0
??????? at TryCatchSequence.main(TryCatchSequence.java:9)
Always executed
程序的運行結果說明:由于命令行沒有給出參數,在執行語句:
b=Integer.parseInt(args[0]);
時,系統拋出了數組下標越界異常。程序中處理數組下標越界異常的catch模塊匹配上了該異常。程序執行完該catch模塊語句后,跳過后面的其它catch模塊,又執行了finally模塊中的語句。
--拋出算術運算異常。
在當前工作目錄下鍵入如下命令(注意:該程序運行時從命令行得到的參數值為0):
java TryCatchSequence 0
程序運行結果如下:
Exception caught!
java.lang.ArithmeticException: / by zero
??????? at TryCatchSequence.main(TryCatchSequence.java:10)
Always executed
程序的運行結果說明:由于命令行給出的參數為0,程序中的被除數為0,因此運行時系統拋出了算術運算異常。程序中處理算術運算異常的catch模塊匹配上了該異常。程序執行完該catch模塊語句后,跳過后面的其它catch模塊,又執行了finally模塊中的語句。
?
【例8.2】程序中設計的catch模塊捕捉不到異常的示例。
要求:程序中設計了若干catch模塊,但忽略了其中某種可能發生的異常。
程序設計如下:
public class TryCatchSequence1
{
?? public static void main(String args[])
?? {
????? int a,b,c;
????? try
????? {??
????????? a=100;
????????? b=Integer.parseInt(args[0]);
????????? c=a/b;
????????? System.out.println("c="+c);??????
????????? System.out.println("No exception.");?????????
????? }
????? catch(ArithmeticException e)
????? {
????????? System.out.println("Exception caught!");
????????? e.printStackTrace();
????? }
????? finally
????? {
?????? System.out.println("Always executed");
????? }
?? }
}
程序設計說明:上述程序和例8.1的程序基本相同,只是去掉了前面程序的catch(ArrayIndexOutOfBoundsException e)模塊。
程序運行結果如下:
Always executed
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
??????? at TryCatchSequence2.main(TryCatchSequence2.java:9)
程序的運行結果說明:程序中出現的ArrayIndexOutOfBoundsException異常不能和程序中的任何catch模塊匹配,因此交給系統處理,系統內置的異常處理模塊輸出了相應的錯誤信息。
?
?
4、異常的拋出和處理
?
??? 系統的異常類定義了很多異常,如果程序運行時出現了系統定義的異常,系統會自動拋出。此時,若應用程序中有try-catch語句,則這些異常由系統捕捉并交給應用程序處理;若應用程序中沒有try-catch語句,則這些異常由系統捕捉和處理。
??? 對于系統定義的有些應用程序可以處理的異常,一般情況下并不希望由系統來捕捉和處理,也不希望這種異常造成后果,因為這兩種情況都有可能造成運行的應用程序產生不良后果。這種情況下,設計應用程序的一般方法是:在try模塊中,應用程序自己判斷是否有異常出現,如果有異常出現,則創建異常對象并用throw語句拋出該異常對象;在catch模塊中,設計用戶自己希望的異常處理方法。
??? throw語句的語法格式為:
??? throw <異常類對象>;?
??? 這種情況下,異常的拋出和處理有兩種方式:一種是在同一個方法中拋出異常和處理異常,另一種是在一個方法中拋出異常,在調用該方法的方法中處理異常。
?
1、在同一個方法中拋出異常和處理異常
?
??? 在同一個方法中拋出異常和處理異常。應用程序中,大部分異常的拋出和異常的處理采用這種設計方法。
?
【例8.3】在同一個方法中拋出異常和處理異常示例。
要求:設計一個堆棧類,堆棧類中要求有一個入棧方法,該方法向堆棧中加入一個元素。當調用入棧方法且堆棧已滿時會出現堆棧已滿異常。處理異常的模塊捕捉到該異常后,顯示異常信息后退出系統。
程序設計如下:
class SeqStack??? //堆棧類
{
int data[];????? //成員變量,存放元素
int MAX;???????? //成員變量,數組個數
int top;??????? //成員變量,當前元素個數
?
SeqStack(int m) //構造方法
{
? MAX = m;?????? //為MAX賦值m
? data = new int[MAX];??? //創建數組對象data
? top = 0;??????? //初始值為0
}
?
public void push(int item)??? //入棧方法
{
? try???????? //try模塊
? {
?? if(top == MAX)???? ? //判斷是否有異常產生
??? throw new Exception("stackFull"); //拋出異常對象
?? data[top] = item;??? //在堆棧中加入元素item
?? top++;?????? //元素個數加1
? }???
? catch(Exception e)????? //catch模塊
? {
?? System.out.println("exception:"+e.getMessage());
?? System.exit(0);
? }
}
?
public static void main(String args[])
{
???? SeqStack stack = new SeqStack(10);? //創建對象stack
???? for(int i = 0; i <11; i++)
???? stack.push(i);????? //調用push()方法
}???
}
程序運行結果如下:
exception:stackFull
程序設計說明:
(1)main()中第一條語句創建的對象stack的數組個數MAX為10,但循環語句最后一次執行時,要向對象stack的數組data中加入第11個元素,所以此時會產生異常。
(2)由于push()方法中的異常情況(top == MAX)不是系統定義的異常,所以,系統不會拋出該異常,需要在該方法中用throw語句自己拋出異常。
(3)拋出異常語句throw new Exception("stackFull")首先調用Exception類的構造方法創建一個異常信息為"stackFull"的異常對象,然后拋出該異常。
(4)push()方法中的catch模塊捕捉到異常后進行了所要求的異常處理。
?
2、拋出異常和處理異常的方法不是同一個
??? 如果拋出異常的方法和處理異常的方法不是同一個方法時,則要求在拋出異常的方法定義后加如下語句:
??? throws Exception
??? 然后,把catch模塊放在調用該方法的方法中。
??? throws Exception語句的功能,是在調用方法和可能產生異常的被調用方法之間建立起系統處理異常所需的聯系。
?
【例8.4】拋出異常和處理異常的方法不是同一個示例。
要求:問題同例8.3,但要求拋出異常和處理異常的方法不是同一個。
程序設計如下:
class SeqStack2
{
int data[];
int MAX;
int top;
?
SeqStack2(int m)??
{
? MAX = m;
? data = new int[MAX];
? top = 0;
}
?
public void push(int item) throws Exception //加throws語句
{
? if(top == MAX)??????? //判斷是否有異常產生
?? throw new Exception("stackFull"); //拋出異常
? data[top] = item;
? top++;
}
?
public static void main(String args[])
{?
???? SeqStack2 stack = new SeqStack2(10);
???? try????????? //try模塊
???? {
????? for(int i = 0; i <11; i++)
?????? stack.push(i);
???? }
? catch(Exception e)????? //catch模塊
? {
?? System.out.println("exception:"+e.getMessage());
?? System.exit(0);
? }??????
}???
}
程序運行結果和例8.3相同。
??? 需要說明的是,此例子只是為了說明此種設計方法,像例8.3這類問題應用程序的異常拋出和異常處理通常采用前一種設計方法。
??? 但是,Java API中的許多可能出現異常的方法都采用此種設計方法。例如,第9章討論的輸入輸出流類中的許多方法在調用時都可能出現異常,而調用這些方法的方法是在用戶編寫的應用程序中,Java API不可能設計出適合所有應用情況的異常處理方法,因此,Java API的這些方法中沒有設計try模塊和catch模塊,只是在方法定義后添加了throws語句。如下方法定義就是一個例子:
??? public void close()? throws IOException
??????? ? //表示close()方法可能拋出IOException異常
??? 如果應用程序調用該方法,一般情況下,要在調用方法中設計try模塊和catch模塊,來處理IOException異常。IOException類是Exception類的一個子類。
?
?
5、自定義的異常類
??? 應用程序中除了可能出現系統定義的異常外,有時還可能出現系統沒有考慮的異常。此時需要在應用程序中自定義異常類。一般情況下,自定義的異常類都是一些應用程序可以處理的異常。所以,自定義的異常類一般是Exception類的子類。
?
【例8.5】自定義的異常類示例。
class UserDefinedException extends Exception
{
?? String? msg;
?? public UserDefinedException()???????
?? {
????? this.msg = "";
?? }
?? public UserDefinedException(String s)????????
?? {
????? this.msg = s;
?? }???
}
例8.5中,用戶自己定義的異常類UserDefinedException繼承了類Exception,類UserDefinedException中定義了兩種情況的異常,一種的參數為空,另一種的參數為字符串s。