Thinking in java 第12章 通過異常處理錯誤 筆記+習題

Thinking in java 第12章 通過異常處理錯誤

學習目錄


12.1 概念

1. 使用一場所帶來的一個好處是,它忘完更夠降低錯誤處理代碼的複雜度。如果不使用異常,那麼就必須檢查特定的的錯誤,並在程序中的許多地方去處理它。而如果使用異常,就不必在方法調用處進行檢查,並且只需在一個地方處理錯誤,即異常處理程序中。這使得代碼的閱讀、編寫和調試工作更加井井有條。

 

12.2 基本異常

1. 同Java其他對象的創建一樣,將使用new在堆上創建異常對象。

2. 異常最重要的方面之一就是如果發生問題,他們將不允許程序沿着其正常的路徑繼續走下去。

3. 所有標準異常類都有兩個構造器:一個是默認構造器,一個是接受字符串作爲參數,以便能把相關信息放入異常對象的構造器。

throw new NullPointerException("t = null");

4. 異常返回的“地點”和普通方法調用返回的“地點”不同,可能離得很遠,也可能跨越方法調用棧很多層次。

5. 能夠拋出任意類型的Throwable對象是異常類型的根類。

 

12.3 捕獲異常

1. 格式如下:

try{
    //Code that might generate exceptions
} catch (Type1 id1) {
    // Handle exceptions of Type1
} catch (Type2 id2) {
    // ...
} ...

2. 在try塊內部,許多不同的方法調用可能會產生類型相同的異常,而你只需要提供一個針對此類型的異常處理程序。

3. 異常處理理論上有兩種基本模型:中止模型和恢復模型。Java支持中止模型,如果想要Java實現類似恢復的行爲,那麼在遇到錯誤時就不能拋出異常,而是調用方法來修正。或者把try塊放在while裏,直到得到滿意的結果。

 

12.4 創建自定義異常

1. 要自己定義異常類,必須從已有的異常類繼承,最好是選擇意思詳盡的異常類繼承(不過並不容易找)。

class SimpleException extends Exception {
    SimpleException() {}
    SimpleException(String msg) { super(msg); }
}

public class A {
    public void f() throws SimpleException {
        print("...");
        throw new SimpleException();
    }
    public static void main(String[] args) {
        try {
            new A().f();
        } catch(SimpleException e) {
            print("Catch!");
        }
    }
}

2. printStackTrace()方法是在Throwable中的(Exception繼承它),它將打印“從方法調用處直到異常跑出處”的方法調用序列。可加上參數“System.out”直接顯示在輸出中,默認版本則被輸出到標準錯誤流(System.err)。

3. 異常與記錄日誌(P255例,看完18章再回來看)。

 

12.5 異常說明

1. 異常說明屬於方法聲明的一部分,緊跟在形式參數列表之後。

2. 可以不止一個。也可以聲明瞭之後不拋出,但拋出後要麼聲明要麼處理。

 

12.6 捕獲所有異常

1. 舉例說明了一些方法,如:printStackTrace()、getStackTrace()(用於配合後者進行遍歷棧軌跡)、StackTraceElement、fillInStackTrace()(更新異常發生地)。

2. 異常鏈:在捕獲一個異常後拋出另一個異常,並希望把原始異常信息保存下來。

3. 現在所有Throwable的子類在構造其中都可以機收一個cause對象作爲參數,而只有Error(用於Java虛擬機報告系統錯誤)、Exception、RuntimeException提供了帶cause參數的構造器,其他類型要用initCause()方法而不是構造器。

 

12.8 使用finally進行清理

1. 對於一些代碼,可能會希望無論try塊中的異常是否拋出,它們都能得到執行。這通常適用於內存回收之外的情況。

try{
    //Code that might generate exceptions
} catch (Type1 id1) {
    // Handle exceptions of Type1
} catch (Type2 id2) {
    // ...
} finally {
    // ...
}

2. 用途:把除內存之外的資源恢復到它們的初始狀態,包括:已經打開的文件或網絡鏈接,在屏幕上畫的圖形,甚至可以使外部世界的某個開關。

3. 在異常沒有被當前的異常處理程序捕獲的情況下,異常處理機制會在跳到更高一層的異常處理程序之前執行finally字句。即如果有嵌套try塊,在進入裏面的try塊之前,外面的finally子句會被執行。

4. 在finally類內部,從何處返回無關緊要,一定會被執行。

5. Java中異常也容易丟失,比如被try塊中的異常被finally中的異常覆蓋。

 

12.11 異常匹配

1. 派生類的對象也可以匹配其基類的處理程序,且只會被處理一次。

2. 如果把捕獲的基類放在派生類之前,編譯器會報錯。

 

12.12 其他可選方式

1. 異常處理的一個重要原則是:只有在你知道如何處理的情況下才捕獲異常。

2. 編譯器會在你還沒準備好的時候就強制讓你加上catch字句(即暫時放置,後面可能就忘了),可能會導致異常被吞食。

 

12.13 異常使用指南

1. 應該在下列情況下使用異常:

  • 在恰當的級別處理問題。(在知道該如何處理的情況下才捕獲異常)
  • 解決問題並且重新調用產生異常的方法。
  • 進行少許修補,然後繞過異常發生的地方繼續執行。
  • 用別的數據進行計算,以代替方法預計會返回的值。
  • 把當前運行環境下能做的事情儘量做完,然後把相同的異常重拋到更高層。
  • 把當前運行環境下能做的事儘量做完,然後把不同的異常拋到更高層。
  • 終止程序。
  • 進行簡化。(如果你的異常模式使問題變得太複雜,那用起來會非常痛苦也很煩人)
  • 讓類庫和程序更安全。(這既是在爲調試做短期投資,也是在爲程序的健壯性做長期投資)

 


習題:

練習1:編寫一個類,在其main()方法的try塊裏拋出一個Exception類的對象。穿第一個字符串參數給Exception的構造器。在catch子句裏捕獲此異常對象,並且打印字符串參數。添加一個finally子句,打印一條信息以證明這裏確實得到了執行。

package Chapter12;

public class E1 {
    public static void main(String[] args) {
        try {
            throw new E1A("aaa");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            System.out.println("this is finally");
        }
    }
}

class E1A extends Exception {
    public E1A() {}
    public E1A(String msg) { super(msg); }
}

/*
aaa
this is finally
*/

練習2:定義一個對象引用並初始化爲null,嘗試用此引用調用方法。把這個引用放在try-catch子句裏以捕獲異常。

package Chapter12;

public class E2 {
    public static void main(String[] args) {
        String s = null;
        //s.length();
        try {
            System.out.println(s.length());
        } catch (NullPointerException e) {
            System.out.println("this is a null pointer!");
        }
    }
}

/*
this is a null pointer!
*/

直接用會拋出NullPointerException異常。

練習3:編寫能產生並能捕獲ArrayIndexOutOfBoundsException異常的代碼。

package Chapter12;

public class E3 {
    public static void main(String[] args) {
        int[] a = new int[] {1,2,3,4,5};
        try {
            System.out.println(a[10]);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Warning: out of bounds!");
        }
    }
}

/*
Warning: out of bounds!
*/

練習4:使用extends關鍵宇建立-個自定義異常類。爲這個類寫一個接受字符串參數的構造器,把此參數保存在對象內部的字符串引用中。寫一個方法顯示此字符串。寫- -個try-catch子句,對這個新異常進行測試。

略。同練習1。

練習5:使用while循環建立類似恢復模型的異常處理行爲,它將不斷重複,直到異常不再拋出。

package Chapter12;

public class E4 {
    public static void main(String[] args) {
        int[] a = new int[] {1,2,3,4,5};
        int index = 10;
        while(true) {
            try {
                System.out.println(a[index]);
                break;
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("" + index + " is out of bounds!");
                index--;
            }
        }
    }
}

/*
10 is out of bounds!
9 is out of bounds!
8 is out of bounds!
7 is out of bounds!
6 is out of bounds!
5 is out of bounds!
5
*/

練習6-練習7 日誌記錄。  略。

練習8:定義一個類,令其方法拋出在練習2裏定義的異常。不用異常說明,看看能否通過編譯。然後加上異常說明,用try-catch子句測試該類和異常。

package Chapter12;

public class E8 {
    public static void main(String[] args) {
        try {
            new E8A().func1();
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("this is a ArrayIndexOutOfBoundsException");
        }
    }
}

class E8A {
    public void func1() throws ArrayIndexOutOfBoundsException {
        throw new ArrayIndexOutOfBoundsException();
    }
}

/*
this is a ArrayIndexOutOfBoundsException
*/

因爲ArrayIndexOutOfBoundsException是RuntimeException的子類所以不聲明也可以,自定義的要聲明。

練習9:定義三種新的異常類型。寫一個類,在一個方法中拋出這三種異常。在main()方法裏調用這個方法,僅用一個catch子句捕獲這三種異常。

。。。?

package Chapter12;

public class E9 {
    public static void main(String[] args) {
        try {
            func();
        } catch (E9C e) {
            e.printStackTrace(System.out);
        }
    }
    public static void func() throws E9C {
        try {
            throw new E9A();
        } catch (E9A e) {
            e.printStackTrace(System.out);
        }

        try {
            throw new E9B();
        } catch (E9B e) {
            e.printStackTrace(System.out);
            throw new E9C();
        }
    }
}

class E9A extends Exception {}
class E9B extends Exception {}
class E9C extends Exception {}

/*
Chapter12.E9A
	at Chapter12.E9.func(E9.java:13)
	at Chapter12.E9.main(E9.java:6)
Chapter12.E9B
	at Chapter12.E9.func(E9.java:19)
	at Chapter12.E9.main(E9.java:6)
Chapter12.E9C
	at Chapter12.E9.func(E9.java:22)
	at Chapter12.E9.main(E9.java:6)
*/

練習10:爲一個類定義兩個方法:f() 和 g() 。在g()裏,拋出一個自定義的新異常。在f()裏調用g,捕獲它拋出的異常,並且在catch子句裏拋出另一個異常(自定義的第二種異常),在main()方法裏測試代碼。

略。同上。

練習11:重複上一個練習,但是在catch子句裏把g()要突出的異常包裝成一個RuntimeException。

意義不明。略。

練習12:修改innerclass/Sequence.java,使其在你試圖向其中放置過多地元素時拋出一個合適的異常。

略。if語句throw一個自定義異常。

練習13:修改練習9,加一個finally子句。驗證一下,即便是拋出NullPointerException異常,finally子句也會得到執行。

。。。?

練習14:試說明,在OnOffSwitch.java的try塊內拋出RuntimeException,程序可能會出錯誤。

因爲要被catch的異常沒有RuntimeException或者其基類。

練習15:試說明,在WithFinally.java的try塊內拋出RuntimeException,程序不會出錯誤。

?爲什麼。

練習16:修改reusing/CADSystem.java,以演示try-finally的中間返回仍會執行正確的清理。

略。知道finally肯定會執行就行了。

練習17:修改polymorphism/Frog.java,使其使用try-finally來保證正確的清理,並展示即使在try-finally的中間返回,它也可以起作用。

同上。

練習18:爲LostMessage.java添加第二層異常丟失,以便用第三個異常來替代HoHumException異常。

package Chapter12;

public class E18 {
    public static void main(String[] args) {
        try {
            try {
                f();
            } finally {
                try {
                    g();
                } finally {
                    dispose();
                }
            }
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    public static void f() throws E18A {
        throw new E18A();
    }

    public static void g() throws E18B {
        throw new E18B();
    }

    public static void dispose() throws E18C {
        throw new E18C();
    }
}

class E18A extends Exception {
    public String toString() {
        return "E18A";
    }
}

class E18B extends Exception {
    public String toString() {
        return "E18B";
    }
}

class E18C extends Exception {
    public String toString() {
        return "E18C";
    }
}

/*
E18C
*/

練習19:通過確保finally子句的調用,來修復LostMessage.java中的問題。

class VeryImportantException extends Exception {
	public String toString() {
		return "A very important exception!";
	}
}

class HoHumException extends Exception {
	public String toString() {
		return "A trivial exception";
	}
}

public class LostMessageFound19 {
	void f() throws VeryImportantException {
		throw new VeryImportantException();
	}
	void dispose() throws HoHumException {
		throw new HoHumException();
	}
	public static void main(String[] args) {
		try {
			LostMessageFound19 lmf = new LostMessageFound19();
			try {
				lmf.f();
			} catch(Exception e) {
				System.out.println(e);
			} finally {
				lmf.dispose();
			}

		} catch(Exception e) {
			System.out.println(e);
		}
	}
}

練習20:修改StormyInning.java,加一個UmpireArgument異常,和一個能拋出此異常的方法。測試一下修改後的異常繼承體系。

略。

練習21:試證明,派生類的構造器不能捕獲它的基類構造器所拋出的異常。

package Chapter12;

public class E21 {

}

class E21A extends Exception {}

class E21B {
    E21B() throws E21A {
        throw new E21A();
    }
}

class E21C extends E21B {
    E21C() throws E21A {
        //super();
        try {
            super();
        } catch (E21A e) {
            System.out.println("catch!");
        }
    }
}

報錯,提示'super()' must be first statement in constructor body。

練習22:創建一個名爲FailingConstructor.java的類,它具有一個可能會在構造過程中失敗並且會拋出一個異常的構造器。在main()中,編寫能夠確保不出現故障的代碼,並在main()中驗證所有可能的故障情形都被覆蓋了。

package Chapter12;

public class E22 {
    public static void main(String[] args) {
        try {
            E22FailingConstructor e = new E22FailingConstructor("aaa");
        } catch (Exception e) {
            System.out.println("Failed");
        } finally {
            System.out.println("End");
        }
    }
}

class E22FailingConstructor {
    String s;

    public E22FailingConstructor(String s) throws Exception {
        this.s = s;
    }
}

練習23:在前一個練習中添加dispose()方法。修改FailingConstructor,使其構造器可以將那些可去除對象之一當做一個成員對象創建,然後改構造器可能會拋出一個異常,之後他將創建第二個可去除成員對象。編寫能夠確保不出故障的代碼,並在main()中驗證所有可能的故障情形都被覆蓋了。

package Chapter12;

public class E23 {
    public static void main(String[] args) {
        try {
            E23FailingConstructor e = new E23FailingConstructor("aaa");
        } catch (Exception e) {
            System.out.println("e failed");
        }finally {
            System.out.println("End");
        }
    }
}

class E23Dispose {
    private boolean isDispose = false;

    public boolean isDispose() {
        return isDispose;
    }

    public void dispose() { isDispose = true; }
}

class E23FailingConstructor {
    String s;

    public E23FailingConstructor(String s) throws Exception {
        try {
            E23Dispose d1 = new E23Dispose();
            try {
                E23Dispose d2 = new E23Dispose();
            } catch (Exception e) {
                System.out.println("d2 failed");
                d1.dispose();
            }
        } catch (Exception e) {
            System.out.println("d1 failed");
        } finally {
            this.s = s;
        }
    }
}

練習24:在FailingConstructor類中添加一個dispose()方法,並編寫代碼正確使用這個類。

略。

練習25:建立一個三層的異常繼承體系,然後創建基類A,它的一個方法能拋出異常體系的基類異常,讓B繼承A,並且覆蓋這個方法,讓它拋出第二層的異常。讓C繼承B,再次覆蓋這個方法,讓它拋出第三層的異常。在main()裏創建一個C類型的對象,把它向上轉型爲A,然後調用這個方法。

略。同繼承。

練習26:沒有練習26?

練習27-練習30。略。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章