1. 程式人生 > >Java 學習(四)—— 異常處理

Java 學習(四)—— 異常處理

一、異常的概念

  異常指的是執行期出現的錯誤,也就是當程式開始執行以後執行期出現的錯誤。出現錯誤時觀察錯誤的名字和行號最為重要。

     異常發生的原因

  • 使用者輸入了非法資料。

  • 要開啟的檔案不存在。

  • 網路通訊時連線中斷,或者JVM記憶體溢位。

二、異常的分類

  • 檢查性異常(Exception)使用者錯誤或問題引起的異常,這是程式設計師無法預見的。是異常的父類,子類需要使用者顯式宣告或捕獲;例如要開啟一個不存在檔案時,一個異常就發生了,這些異常在編譯時不能被簡單地忽略。

  • 執行時異常(Runtime Exception)

    : 執行時異常是可能被程式設計師避免的異常。與檢查性異常相反,執行時異常可以在編譯時被忽略。例如,除數為0,陣列下標超出範圍等。

  • 錯誤(ERROR):由Java虛擬機器生成並丟擲,錯誤不是異常,而是脫離程式設計師控制的問題。錯誤在程式碼中通常被忽略。例如,棧溢位,一個錯誤就發生了,它們在編譯也檢查不到的;動態連結失敗;虛擬機器錯誤。

Exception的層次

    所有的異常類是從java.lang.Exception類繼承的子類,Exception類是Throwable類的子類。除了Exception類外,Throwable還有一個子類Error 。

Java程式通常不捕獲錯誤。錯誤一般發生在嚴重故障時,它們在Java程式處理的範疇之外。

    Throwable類:可以被丟擲的,是所有異常類的根基類,所有的異常類都是從它繼承。

    Error類:是不能處理的錯誤。Java虛擬機器(JVM)執行時出錯,用來指示執行時環境發生的錯誤。例如,JVM記憶體溢位。一般地,程式不會從錯誤中恢復。

    IOException......類:這些錯誤必須被處理;

    Runtime Exception類:不一定都要處理,可以忽略;


三、異常的捕獲和處理

    Java異常處理的五個關鍵字:try、catch、finally、throw、throws

捕獲異常

    使用try和catch關鍵字可以捕獲異常。try/catch/finally程式碼塊放在異常可能發生的地方。

    try/catch程式碼塊中的程式碼稱為保護程式碼,使用 try/catch/finally的語法如下:

try
{
   // 可能丟擲異常的語句
}catch(ExceptionName e1)
{
   //Catch 塊
}catch(ExceptionName e1)
{
   //Catch 塊
}finally{
    ...
}

    try語句:捕獲可能丟擲異常的語句,如果有例外異常,交給後面的catch語句進行相應處理,後面可以跟一個或多個catch程式碼段。 

    Catch語句:包含要捕獲異常型別的宣告。當保護程式碼塊中發生一個異常時,try後面的catch塊就會被檢查。

如果發生的異常包含在catch塊中,異常會被傳遞到該catch塊處理。在catch程式碼塊中可以用一些方法獲取異常資訊

    getMessage()方法:用來得到有關異常事件的資訊。

    printStackTrace()方法:一般使用這種方法,包括了getMessage()方法,用來跟蹤異常事件發生時執行堆疊的內容。使用前需要 new 一個錯誤物件再呼叫,因為它是專屬某個錯誤物件裡面的方法,

    finally語句:為異常處理提供的統一出口,無論是否發生異常都執行,可以不寫。在finally語句中,可以進行資源的清除工作,例如,開啟關閉檔案、刪除臨時檔案...

例項

下面的例子中宣告有兩個元素的一個數組,當代碼試圖訪問陣列的第三個元素的時候就會丟擲一個異常。

import java.io.*;
public class ExcepTest{

public static void main(String args[]){
   try{
      int a[] = new int[2];
      System.out.println("Access element three :" + a[3]);  //可能丟擲異常語句
   }catch(Exception e1){                                    //捕獲陣列下標越界異常
      System.out.println("陣列下標越界"); 
   }catch(ArrayIndexOutOfBoundsException e2){
       System.out.println("異常發生");                      //輸出錯誤資訊
   }
 }
}

因為Exception類中包含ArrayIndexOutOfBoundsException異常,所以第二個catch異常重複,是不允許的,編譯不會通過。

以上程式碼編譯執行輸出結果如下:

Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 

throws/throw關鍵字:

如果一個方法沒有捕獲一個檢查性異常,那麼該方法必須使用throws 關鍵字來宣告。throws關鍵字放在方法簽名的尾部。

也可以使用throw關鍵字丟擲一個異常,無論它是新例項化的還是剛捕獲到的。

下面方法的宣告丟擲一個RemoteException異常:

import java.io.*;
public class className
{
   public void deposit(double amount) throws RemoteException
   {
      // Method implementation
      throw new RemoteException();
   }
   //Remainder of class definition
}

一個方法可以宣告丟擲多個異常,多個異常之間用逗號隔開。

例如,下面的方法宣告丟擲RemoteException和InsufficientFundsException:

import java.io.*;
public class className
{
   public void withdraw(double amount) throws RemoteException,
                              InsufficientFundsException
   {
       // Method implementation
   }
   //Remainder of class definition
}

測試異常

package test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class testException {
	
	/**
	 * 任何方法往外拋能處理的異常的時候都有一種簡單的寫法:“throws Exception”,
	 * 因為Exception類是所有能處理的異常類的根基類,因此丟擲Exception類就會丟擲所有能夠被處理的異常類裡了。
	 * 使用“throws Exception”丟擲所有能被處理的異常之後,這些被丟擲來的異常就是交給JAVA執行時系統處理了,
	 * 而處理的方法是把這些異常的相關錯誤堆疊資訊全部打印出來。
	 * @throws Exception
	 */
	void fn() throws Exception{
		
	}
	
	/**
	 * 已知異常的型別,方法宣告時使用throws把異常往外拋
	 * @param i
	 * @throws ArithmeticException
	 */
	void m1(int i) throws ArithmeticException{
		
	}
	
	/**
	 * 手動丟擲異常
	 * new了一個異常物件,在構建這個物件的時候還可以指定他相關的資訊,如這裡指明瞭異常資訊“i不能等於0”
	 * 這個物件丟擲去的時候使用getMessage()方法拿到的就是“i不能等於0”這種資訊。
	 * @param i
	 */
	void m2(int i) {
		if (i==0) {
			throw new ArithmeticException("i不能等於0");
		}
	}
	
	/**
	 * 正常情況下如果這裡不寫try……catch語句那麼程式編譯時一定會報錯,
     * 因為這裡有可能會產生兩個個必須要處理的異常:FileNotFoundException和IOException。
     * 但由於在宣告方法f()時已經使用throws把可能產生的這兩個異常丟擲了,
     * 所以這裡可以不寫try……catch語句去處理可能會產生的異常。
     * f()方法把丟擲的異常交給下一個要呼叫它的方法去處理
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	void f()throws FileNotFoundException,IOException {
		//這裡有可能會產生FileNotFoundException異常
		FileInputStream fis=new FileInputStream("1.txt");
		//這裡有可能會產生IOException異常
		int b = fis.read();
		while (b!=-1) {
			System.out.println((char)b);
			b=fis.read();
		}
	}
	
	/**
     * 在f2()方法裡面呼叫f()方法時必須要處理f()方法丟擲來的異常,
     * 當然,如果f2()方法也沒有辦法處理f()方法丟擲來的異常,那麼f2()方法也可以使用throws把異常丟擲,
     * 交給下一個呼叫了f2()的方法去處理f()方法丟擲來的異常。
     * 這裡f2()呼叫f()方法時,選擇不處理f()方法中可能丟擲的異常,將異常繼續丟擲
     * @throws Exception
     */
    void f2() throws Exception {
        f();
    }
    
    /**
     * f3方法呼叫f方法捕獲f()方法丟擲的2個異常並進行處理
     */
    void f3() {
        try {
            f();
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());		//處理的方法是把錯誤資訊打印出來
        } catch (IOException e) {
            e.printStackTrace();					//處理的方法是使用printStackTrace()方法把錯誤的堆疊資訊全部打印出來。
        }
    }
    
    public static void main(String[] args) {
    	FileInputStream fis = null;
        try {
            fis = new FileInputStream("MyFile.txt");
            int b = fis.read();					//這個有可能會丟擲IOException異常
            while (b != -1) {
                System.out.println((char)b);
                b = fis.read();
            }
        } catch (FileNotFoundException e) {
            //使用catch捕獲FileNotFoundException類異常的異常物件e。並讓異常物件e自己呼叫printStackTrace方法打印出全部的錯誤資訊
            e.printStackTrace();
        } catch (IOException e) {
            //再次使用catch捕獲IOException類的異常物件e,並讓異常物件e自己呼叫getMessage()方法將錯誤資訊打印出來。
            System.out.println(e.getMessage());;
        }finally{
            try {
                /**
                 * 前面已經把一個檔案打開了,不管開啟這個檔案時有沒有錯誤發生,即有沒有產生異常,最後都一定要把這個檔案關閉掉,
                 * 因此使用了finally語句,在finally語句裡面不管前面這個檔案開啟時是否產生異常,在finally這裡執行in.close()都能把這個檔案關閉掉,
                 * 關閉檔案也有可能會產生異常,因此在finally裡面也使用了try……catch語句去捕獲有可能產生的異常。
                 */
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }	
}
以上程式碼編譯執行輸出結果如下:
java.io.FileNotFoundException: MyFile.txt (系統找不到指定的檔案。)
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(FileInputStream.java:195)
	at java.io.FileInputStream.<init>(FileInputStream.java:138)
	at java.io.FileInputStream.<init>(FileInputStream.java:93)
	at test.testException.main(testException.java:88)
Exception in thread "main" java.lang.NullPointerException
	at test.testException.main(testException.java:107)

四、自定義異常

編寫自己的異常類時需要記住下面的幾點。

  • 繼承java.lang.Exception;宣告自己的異常類。

  • 如果希望寫一個檢查性異常類,則需要繼承Exception類。

  • 如果你想寫一個執行時異常類,那麼需要繼承RuntimeException 類。

例項:自定義異常

package test;

public class MyException extends Exception{
	private int id;

    /**
     * 自定義異常類的構造方法
     * @param message
     * @param id
     */
    public MyException(String message,int id) {
        super(message);			//呼叫父類Exception的構造方法
        this.id = id;
    }
    
    /**
     * 獲取異常的程式碼
     * @return
     */
    public int getId() {
        return id;
    }
}

例項:自定義異常測試

package test;
import java.text.MessageFormat;

public class TestMyException {
	//throws MyException,丟擲我們自定義的MyException類的異常。
    public void regist(int num) throws MyException {
        if (num < 0) {
            //使用throw手動丟擲一個MyException類的異常物件。
            throw new MyException("人數為負值,不合理", 1);
        }
        /**
         * 注意:當我們丟擲了異常之後,
         * System.out.println(MessageFormat.format("登記人數:{0}",num));是不會被執行的。
         * 丟擲異常之後整個方法的呼叫就結束了。
         */
        System.out.println(MessageFormat.format("登記人數:{0}",num));
    }
    
    public void manage() {
        try {
            regist(-100);
        } catch (MyException e) {
            System.out.println("登記失敗,錯誤碼:"+e.getId());
            e.printStackTrace();
        }
        System.out.println("操作結束");
    }
    
    
    public static void main(String[] args) {
        TestMyException t = new TestMyException();
        t.manage();
    }
}
登記失敗,錯誤碼:1
操作結束
test.MyException: 人數為負值,不合理
	at test.TestMyException.regist(TestMyException.java:9)
	at test.TestMyException.manage(TestMyException.java:21)
	at test.TestMyException.main(TestMyException.java:32)
登記失敗,錯誤碼:1
操作結束
test.MyException: 人數為負值,不合理
	at test.TestMyException.regist(TestMyException.java:9)
	at test.TestMyException.manage(TestMyException.java:21)
	at test.TestMyException.main(TestMyException.java:32)

文章參考:孤傲蒼狼