1. 程式人生 > >Java異常處理知識點

Java異常處理知識點

Java異常體系

ThrowableErroException......IOExceptionRuntimeExceptionEOFExceptionFileNotFoundExceptionArithmeticExceptionNullPointerExceptionUnknowTypeExceptionClassNotFoundExceptionMissingResourceExceptionArrayIndexOutOfBoundsException......

從圖中可以看出所有異常型別都是內建類Throwable的子類,因而Throwable在異常類的層次結構的頂層。
接下來Throwable分成了兩個不同的分支,一個分支是Error

,它表示不希望被程式捕獲或者是程式無法處理的錯誤。另一個分支是Exception,它表示使用者程式可能捕捉的異常情況或者說是程式可以處理的異常。其中異常類Exception又分為執行時異常(RuntimeException)和非執行時異常。
Java異常又可以分為不受檢查異常(Unchecked Exception)和檢查異常(Checked Exception)

這些異常之間的區別與聯絡:

  • Error:Error類物件由 Java 虛擬機器生成並丟擲,大多數錯誤與程式碼編寫者所執行的操作無關。

  • Exception:在Exception分支中有一個重要的子類RuntimeException

    (執行時異常),該型別的異常自動為你所編寫的程式定義ArrayIndexOutOfBoundsException(陣列下標越界)、NullPointerException(空指標異常)、ArithmeticException(算術異常)、MissingResourceException(丟失資源)、ClassNotFoundException(找不到類)等異常,這些異常是不檢查異常,程式中可以選擇捕獲處理,也可以不處理。這些異常一般是由程式邏輯錯誤引起的,程式應該從邏輯角度儘可能避免這類異常的發生;而RuntimeException 之外的異常我們統稱為非執行時異常,型別上屬於Exception類及其子類,從程式語法角度講是必須進行處理的異常
    ,如果不處理,程式就不能編譯通過。如IOExceptionSQLException等以及使用者自定義的Exception異常,一般情況下不自定義檢查異常

    Error和Exception的區別:Error通常是災難性的致命的錯誤,是程式無法控制和處理的,當出現這些異常時,Java虛擬機器(JVM)一般會選擇終止執行緒;Exception通常情況下是可以被程式處理的,並且在程式中應該儘可能的去處理這些異常。

  • 檢查異常:在正確的程式執行過程中,很容易出現的、情理可容的異常狀況,在一定程度上這種異常的發生是可以預測的,並且一旦發生該種異常,就必須採取某種方式進行處理

    除了RuntimeException及其子類以外,其他的Exception類及其子類都屬於檢查異常,當程式中可能出現這類異常,要麼使用try-catch語句進行捕獲,要麼用throws子句丟擲,否則編譯無法通過。

  • 不受檢查異常:包括RuntimeException及其子類和Error。

處理異常的兩種方式

1. 捕獲異常(try-catch-finally)

try{
	//可能發生異常的程式碼塊
}catch(可以捕獲的異常1{
	//處理異常1的程式碼
}catch(可以捕獲的異常2{
	//處理異常2的程式碼
}finally{
	//處理完所有異常後一定會執行的程式碼。
	//在這裡注意的事。如果在這裡沒有出現異常,最終也會執行這行程式碼
}

注意:在這裡,格式是可以發生變化的,我們可以增加catch塊,也可以不需要finally塊。

使用多重的catch語句:很多情況下,由單個的程式碼段可能引起多個異常。處理這種情況,我們需要定義兩個或者更多的catch子句,每個子句捕獲一種型別的異常,當異常被引發時,每個catch子句被依次檢查,第一個匹配異常型別的子句執行,當一個catch子句執行以後,其他的子句將被旁路。

編寫多重catch語句塊注意事項
  順序問題:先小後大,即先子類後父類
  
巢狀try語句:try語句可以被巢狀。也就是說,一個try語句可以在另一個try塊的內部。每次進入try語句,異常的前後關係都會被推入堆疊。如果一個內部的try語句不含特殊異常的catch處理程式,堆疊將彈出,下一個try語句的catch處理程式將檢查是否與之匹配。這個過程將繼續直到一個catch語句被匹配成功,或者是直到所有的巢狀try語句被檢查完畢。如果沒有catch語句匹配,Java執行時系統將處理這個異常。
例如:

class NestTry{
    public static void main(String[] args){
        try{
            int a = args.length;
            int b = 42 / a;
            System.out.println("a = "+ a);
            try{
                if(a == 1){
                a = a/(a-a);
                }
                if(a == 2){
                    int c[] = {1};
                    c[42] =99;
                }
            }catch(ArrayIndexOutOfBoundsException e){
                System.out.println("ArrayIndexOutOfBounds :"+e);
            }    
        }catch(ArithmeticException e){
            System.out.println("Divide by 0"+ e);
        }
    }
}

正如程式中所顯示的,該程式在一個try塊中嵌套了另一個try塊。
程式工作如下:當你在沒有命令列引數的情況下執行該程式,外面的try塊將產生一個被0除的異常。
程式在有一個命令列引數條件下執行,由巢狀的try塊產生一個被0除的異常,由於內部的catch塊不匹配這個異常,它將把異常傳給外部的try塊,在外部異常被處理。如果你在具有兩個命令列引數的條件下執行該程式,將由內部try塊產生一個數組邊界異常。
結果:

D:\java>javac estTry.java

D:\java>>java NestTry

Divide by 0 java.lang.ArithmeticExceptio: / by zero

D:\java>java NestTry one

a = 1

Divide by 0java.lang.ArithmeticException: / by zero

D:\java>java NestTry one two

a = 2

ArrayIndexOutOfBounds :java.lang.ArrayIndexOutOfBoundsException: 42

2. 丟擲異常(Throw/Throws)

  • throw

我們還可以用throw語句丟擲明確的異常。
Throw的語法形式如下:

throw ThrowableInstance;

這裡的ThrowableInstance一定是Throwable類型別或者Throwable子類型別的一個物件。
簡單的資料型別,例如int,char,以及非Throwable類,例如String或Object,不能用作異常。

有兩種方法可以獲取Throwable物件:在catch子句中使用引數或者使用new操作符建立。

程式執行完throw語句之後立即停止;
throw後面的任何語句不被執行,最鄰近的try塊用來檢查它是否含有一個與異常型別匹配的catch語句。
如果發現了匹配的塊,控制轉向該語句;如果沒有發現,次包圍的try塊來檢查,以此類推。
如果沒有發現匹配的catch塊,預設異常處理程式中斷程式的執行並且列印堆疊軌跡。
例如:

class TestThrow{
	static void proc(){
		try{
			throw new NullPointerException("demo");	
		}catch(NullPointerException e){
			System.out.printlb("Caught inside proc");
			throw e;
		}
	}
	public static void main(String [] args){
        try{
            proc();
        }catch(NullPointerException e){
            System.out.println("Recaught: "+e);
    }
}

結果:

D:\java>java TestThrow

Caught inside proc

Recaught: java.lang.NullPointerException: demo

該程式兩次處理相同的錯誤,首先,main()方法設立了一個異常關係然後呼叫proc()。proc()方法設立了另一個異常處理關係並且立即丟擲一個NullPointerException例項,NullPointerException在main()中被再次捕獲。

該程式闡述了怎樣建立Java的標準異常物件,特別注意這一行:

throw new NullPointerException("demo");

此處new用來構造一個NullPointerException例項,所有的Java內建的執行時異常有兩個構造方法:一個沒有引數,一個帶有一個字串引數。
當用第二種形式時,引數指定描述異常的字串。如果物件用作print()或者println()的引數時,該字串被顯示。這同樣可以通過呼叫getMessage()來實現,getMessage()是由Throwable定義的。

  • Throws

如果一個方法可以導致一個異常但不處理它,它必須指定這種行為以使方法的呼叫者可以保護它們自己而不發生異常。要做到這點,我們可以在方法宣告中包含一個throws子句。一個throws子句列舉了一個方法可能引發的所有異常型別。這對於除了Error或RuntimeException及它們子類以外型別的所有異常是必要的。一個方法可以引發的所有其他型別的異常必須在throws子句中宣告,否則會導致編譯錯誤

Throws的語法形式如下:

public void info() throws Exception
{
   //body of method
}

Exception 是該方法可能引發的所有的異常,也可以是異常列表,中間以逗號隔開。

例如:

class TestThrows{
    static void throw1() throws IllegalAccessException {
        System.out.println("Inside throw1 . ");
        throw new IllegalAccessException("demo");
    }
    public static void main(String[] args){
        try {
            throw1();
        }catch(IllegalAccessException e ){
            System.out.println("Caught " + e);
        }
    }
}

Throws丟擲異常的規則:

如果是不受檢查異常(unchecked exception),即Error、RuntimeException或它們的子類,那麼可以不使用throws關鍵字來宣告要丟擲的異常,編譯仍能順利通過,但在執行時會被系統丟擲。
必須宣告方法可丟擲的任何檢查異常(checked exception)。即如果一個方法可能出現受可查異常,要麼用try-catch語句捕獲,要麼用throws子句宣告將它丟擲,否則會導致編譯錯誤。
僅當丟擲了異常,該方法的呼叫者才必須處理或者重新丟擲該異常。當方法的呼叫者無力處理該異常的時候,應該繼續丟擲,而不是囫圇吞棗。
呼叫方法必須遵循任何可查異常的處理和宣告規則。若覆蓋一個方法,則不能宣告與覆蓋方法不同的異常。宣告的任何異常必須是被覆蓋方法所宣告異常的同類或子類。

問題擴充套件(面試題):
1、try{} 裡有一個 return 語句,那麼緊跟在這個 try 後的 finally{} 裡的 code 會不會被執行,什麼時候被執行,在 return 前還是後?

答案:會執行,在方法返回呼叫者前執行。

注意:在finally中改變返回值的做法是不好的,因為如果存在finally程式碼塊,try中的return語句不會立馬返回呼叫者,而是記錄下返回值待finally程式碼塊執行完畢之後再向呼叫者返回其值,然後如果在finally中修改了返回值,就會返回修改後的值。顯然,在finally中返回或者修改返回值會對程式造成很大的困擾,C#中直接用編譯錯誤的方式來阻止程式設計師幹這種齷齪的事情,Java中也可以通過提升編譯器的語法檢查級別來產生警告或錯誤,Eclipse中可以在如圖所示的地方進行設定,強烈建議將此項設定為編譯錯誤。

在這裡插入圖片描述

2、Java語言如何進行異常處理,關鍵字:throws、throw、try、catch、finally分別如何使用?

:Java通過面向物件的方法進行異常處理,把各種不同的異常進行分類,並提供了良好的介面。在Java中,每個異常都是一個物件,它是Throwable類或其子類的例項。當一個方法出現異常後便丟擲一個異常物件,該物件中包含有異常資訊,呼叫這個物件的方法可以捕獲到這個異常並可以對其進行處理。Java的異常處理是通過5個關鍵詞來實現的:trycatchthrowthrowsfinally。一般情況下是用try來執行一段程式,如果系統會丟擲(throw)一個異常物件,可以通過它的型別來捕獲(catch)它,或通過總是執行程式碼塊(finally)來處理;try用來指定一塊預防所有異常的程式;catch子句緊跟在try塊後面,用來指定你想要捕獲的異常的型別;throw語句用來明確地丟擲一個異常;throws用來宣告一個方法可能丟擲的各種異常(當然宣告異常時允許無病呻吟);finally為確保一段程式碼不管發生什麼異常狀況都要被執行;try語句可以巢狀,每當遇到一個try語句,異常的結構就會被放入異常棧中,直到所有的try語句都完成。如果下一級的try語句沒有對某種異常進行處理,異常棧就會執行出棧操作,直到遇到有處理這種異常的try語句或者最終將異常拋給JVM

3、執行時異常與受檢異常有何異同?

:異常表示程式執行過程中可能出現的非正常狀態,執行時異常表示虛擬機器的通常操作中可能遇到的異常,是一種常見執行錯誤,只要程式設計得沒有問題通常就不會發生。受檢異常跟程式執行的上下文環境有關,即使程式設計無誤,仍然可能因使用的問題而引發。Java編譯器要求方法必須宣告丟擲可能發生的受檢異常,但是並不要求必須宣告丟擲未被捕獲的執行時異常。異常和繼承一樣,是面向物件程式設計中經常被濫用的東西,在Effective Java中對異常的使用給出了以下指導原則:

  • 不要將異常處理用於正常的控制流(設計良好的API不應該強迫它的呼叫者為了正常的控制流而使用異常)
  • 對可以恢復的情況使用受檢異常,對程式設計錯誤使用執行時異常
  • 避免不必要的使用受檢異常(可以通過一些狀態檢測手段來避免異常的發生)
  • 優先使用標準的異常
  • 每個方法丟擲的異常都要有文件
  • 保持異常的原子性
  • 不要在catch中忽略掉捕獲到的異常

4、列出一些你常見的執行時異常?

  • ArithmeticException(算術異常)
  • ClassCastException (類轉換異常)
  • IllegalArgumentException (非法引數異常)
  • IndexOutOfBoundsException (下標越界異常)
  • NullPointerException (空指標異常)
  • SecurityException (安全異常)

自定義異常

使用Java內建的異常類可以描述在程式設計時出現的大部分異常情況。除此之外,使用者還可以自定義異常。使用者自定義異常類,只需繼承Exception類即可。

在程式中使用自定義異常類,大體可分為以下幾個步驟:

  • 建立自定義異常類。
  • 在方法中通過throw關鍵字丟擲異常物件。
  • 如果在當前丟擲異常的方法中處理異常,可以使用try-catch語句捕獲並處理;否則在方法的宣告處通過throws關鍵字指明要丟擲給方法呼叫者的異常,繼續進行下一步操作。
  • 在出現異常方法的呼叫者中捕獲並處理異常。

舉例自定義異常:

class MyException extends Exception {
   private int detail;
   MyException(int a){
       detail = a;
   }
   public String toString(){
       return "MyException ["+ detail + "]";
   }
}
public class TestMyException{
   static void compute(int a) throws MyException{
       System.out.println("Called compute(" + a + ")");
       if(a > 10){
           throw new MyException(a);
       }
       System.out.println("Normal exit!");
   }
   public static void main(String [] args){
       try{
           compute(1);
           compute(20);
       }catch(MyException me){
           System.out.println("Caught " + me);
       }
   }
}

執行結果如下:

D:\java>java TestMyException

Called compute(1)

Normal exit!

Called compute(20)

Caught MyException [20]

在這裡插入圖片描述