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。略。

你可能感兴趣的:(Thinking,in,Java,Java)