第12章 通过异常处理错误

第12章 通过异常处理错误

java的基本理念是”结构不佳的代码不能运行“

通过这种方式可以使得你更加自信:你的应用中没有未处理的错误。异常处理时Java中唯一正式的错误报告机制,并且通过编译器强制执行。

12.1 概念

把”描述再正常执行过程中做什么事“的代码和”出了问题怎么办的代码相分离“异常使代码相分离。

12.2 基本概念

当抛出异常后

  1. 使用new再堆上创建异常对象
  2. 当前执行路径(他不能继续下去)被终止,并且从当前环境中弹出对异常对象的引用
  3. 异常处理机制接管程序
  4. 寻找一个恰当的地方(异常处理程序)继续执行程序
  5. 将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去

12.2.1 异常参数

用new再堆上创建异常对象,伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器:

  • 一个是默认构造器
  • 另一个接受字符串为参数,以便能把相关信息放入异常对象的构造器

Throw new NullPointerException("t = null");

new创建了异常对象之后,此对象的引用将传给throw。尽管返回的异常对象其类型通常与方法设计的返回类型不同,但从效果上看,它就像使从方法”返回“的。

12.3 捕获异常

监控区域(guarded region)的概念

12.3.1 try块

方法内部抛出异常(方法内部调用其他的方法抛出了异常)

12.3.1 异常处理程序

紧跟在try后,以关键字catch表示(看起来就像是接受一个且仅接受一个特殊类型的方法 )

  • 当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序

  • 然后进入catch子句执行,此时认为异常得到了处理

  • catch子句结束,处理程序的查找过程结束

  • 只有匹配的catch才能执行

    终止与恢复

异常处理理论上有两种基本模型。

  • Java支持终止模型(它是Java和C++所支持的模型)。在这种模型中,一旦异常被抛出,就表明错误已经无法挽回,也不能回来继续执行。
  • 恢复模型。异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。对于恢复模型,通常希望异常被处理之后能继续执行程序。
  • 如果Java像实现类似恢复的行为,那么在遇见错误时就不能抛出异常,而是调用方法来修正该错误,或者把try放进while循环里,这样就不断地进入try块,直到得到满意的结果。
  • 恢复性异常 导致耦合,恢复性程序需要了解异常抛出的地点 , 这势必要包含依赖于抛出位置的非通用性代码。

12.4 创建自定义异常

自己定义异常类来表示程序中可能遇到的特定问题,最好是选择意思相近的异常类继承。

练习1

/* Create a class with a main(0 that throws an object of class Exception
 * inside a try block. Give the constructor for Exception a String argument.
 * Catch the exception inside a catch clause and print the String argument.
 * Add a finally clause and print a message to prove you were there.
 */

class Exception1 extends Exception {
    public Exception1(String msg) {
        super(msg);//super构造基类构造器
        System.out.println("Exception1(String msg)");
    }
}

public class No1Ex {
    public static void f() throws Exception1 {
        System.out.println("Throwing MyException from f()");
        throw new Exception1("From f()");
    }
    public static void main(String[] args) {
        try {
            f();
        } catch(Exception1 e) {
            System.err.println("Caught Exception1");
            e.printStackTrace();
        } finally {
            System.out.println("Made it to finally");
        }

    }
}
=============================================================
    Throwing MyException from f()
Exception1(String msg)
Made it to finally
Caught Exception1
第十二章异常处理.Exception1: From f()
	at 第十二章异常处理.No1Ex.f(No1Ex.java:18)
	at 第十二章异常处理.No1Ex.main(No1Ex.java:22)

练习2

/* Define an object reference and initialize it to null. Try to call a method
 * through this reference. Now wrap the code in a try-catch clause to catch the
 * exception.
 */

public class No2Ex {
    private static Integer i = null;
    public static void main(String[] args) {
        // leads to NullPointerException:
        // System.out.println(i.toString());
        try {
            System.out.println(i.toString());
        } catch(NullPointerException e) {
            System.err.println("Caught NullPointerException");
            e.printStackTrace();
        }
        try {
            i = 10;
            System.out.println(i.toString());
        } catch(NullPointerException e) {
            System.err.println("Caught NullPointerException");
            e.printStackTrace();
        } finally {
            System.out.println("Got through it");
        }
    }
}
=======================================================================
Caught NullPointerException
java.lang.NullPointerException
	at 第十二章异常处理.No2Ex.main(No2Ex.java:14)
10
Got through it

练习3

package 第十二章异常处理;
// Write code to generate and catch an ArrayIndexOutOfBoundsException.

public class No3Ex {
    private static int[] ia = new int[2];
    public static void main(String[] args) {
        try {
            ia[2] = 3;
        } catch(ArrayIndexOutOfBoundsException e) {
            System.err.println(
                    "Caught ArrayIndexOutOfBoundsException");
            e.printStackTrace();
        }
    }
}
=========================================================================
    Caught ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException: 2
	at 第十二章异常处理.No3Ex.main(No3Ex.java:8)

练习4

/* Create your own exception class using the extends keyword. Write a
 * constructor for this class that takes a String argument and stores it inside
 * the object with a String reference. Write a method that displays the stored
 * String. Create a try-catch clause to exercise your new exception.
 */

class Exception4 extends Exception {
    private String msg;
    Exception4(String msg) {
        super(msg);
        System.out.println("Exception4()");
        this.msg = msg;
    }
    protected void showS() {
        System.out.println("Message from Exception4: " + msg);
    }
}

public class No4Ex {
    public static void f() throws Exception4 {
        System.out.println("f()");
        throw new Exception4("Ouch from f()");
    }
    public static void main(String[] args) {
        try {
            f();
        } catch(Exception4 e) {
            System.err.println("Caught Exception4");
            e.printStackTrace();
            e.showS();
        }
    }
}
=============================================================f()
Exception4()
Message from Exception4: Ouch from f()
Caught Exception4
第十二章异常处理.Exception4: Ouch from f()
	at 第十二章异常处理.No4Ex.f(No4Ex.java:24)
	at 第十二章异常处理.No4Ex.main(No4Ex.java:28)

练习5

/* Create you own resumption-like behavior using a while loop that repeats
 * until an exception is no longer thrown.
 */

public class No5Ex {
    private static int[] ia = new int[2];
    static int x = 5;
    public static void main(String[] args) {
        while(true) {
            try {
                ia[x] = 1;
                System.out.println(ia[x]);
                break;
            } catch(ArrayIndexOutOfBoundsException e) {
                System.err.println(
                        "Caught ArrayIndexOutOfBoundsException");
                e.printStackTrace();
                x--;
            } finally {
                System.out.println("Are we done yet?");
            }
        }
        System.out.println("Now, we're done.");
    }
}
========================================================================
Caught ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException: 5
	at 第十二章异常处理.No5Ex.main(No5Ex.java:13)
Caught ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException: 4
	at 第十二章异常处理.No5Ex.main(No5Ex.java:13)
Caught ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException: 3
	at 第十二章异常处理.No5Ex.main(No5Ex.java:13)
Caught ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException: 2
	at 第十二章异常处理.No5Ex.main(No5Ex.java:13)
Are we done yet?
Are we done yet?
Are we done yet?
Are we done yet?
1
Are we done yet?
Now, we're done.

12.4.1 异常与记录日志

使用 java.util.logging 工具将输出记录到日志中。

LoggingException将所有记录日志的基础设施都构建在异常自身中,使得它所使用的方式非常方便。

练习6

/* Create two exception classes, each of which performs its own logging
 * automtically. Demonstrate that these work.
 */
import java.util.logging.*;
import java.io.*;

class Oops1 extends Exception {
    private static Logger logger = Logger.getLogger("LoggingException");//与错误相关的包名或者类名字
    public Oops1() {
        StringWriter trace = new StringWriter();
        printStackTrace(new PrintWriter(trace));//获取抛出处的栈轨迹,接受Java。io。PrintWriter对象作为参数从而产生字符串
        logger.severe(trace.toString());//severe 直接调用与日志级别相关联的方法
    }
}

class Oops2 extends Exception {
    private static Logger logger = Logger.getLogger("LoggingException");
    public Oops2() {
        StringWriter trace = new StringWriter();
        printStackTrace(new PrintWriter(trace));
        logger.severe(trace.toString());
    }
}

public class No6Ex {
    static void f() throws Oops1 {
        throw new Oops1();
    }
    static void g() throws Oops2 {
        throw new Oops2();
    }
    public static void main(String[] args) {
        try {
            f();
        } catch(Exception Oops1) {}
        try {
            g();
        } catch(Exception Oops2) {}
    }
}
=======================================================================
二月 16, 2021 9:44:22 下午 第十二章异常处理.Oops1 <init>
严重: 第十二章异常处理.Oops1
	at 第十二章异常处理.No6Ex.f(No6Ex.java:29)
	at 第十二章异常处理.No6Ex.main(No6Ex.java:36)

二月 16, 2021 9:44:22 下午 第十二章异常处理.Oops2 <init>
严重: 第十二章异常处理.Oops2
	at 第十二章异常处理.No6Ex.g(No6Ex.java:32)
	at 第十二章异常处理.No6Ex.main(No6Ex.java:39)

练习7

// Modify Exercise 3 so that the catch clause logs the result.
import java.util.logging.*;
import java.io.*;

public class No7Ex {
    private static int[] ia = new int[2];
    private static Logger logger = Logger.getLogger("Ex7 Exceptions");
    static void logException(Exception e) { // Exception e argument
        StringWriter trace = new StringWriter();
        e.printStackTrace(new PrintWriter(trace));
        logger.severe(trace.toString());
    }
    public static void main(String[] args) {
        try {
            ia[2] = 3;
        } catch(ArrayIndexOutOfBoundsException e) {
            System.err.println(
                    "Caught ArrayIndexOutOfBoundsException");
            e.printStackTrace();
            // call logging method:
            logException(e);
        }
    }
}
====================================================================
    Caught ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException: 2
	at 第十二章异常处理.No7Ex.main(No7Ex.java:18)
二月 16, 2021 10:04:45 下午 第十二章异常处理.No7Ex logException
严重: java.lang.ArrayIndexOutOfBoundsException: 2
	at 第十二章异常处理.No7Ex.main(No7Ex.java:18)

12.5 异常说明

Java鼓励人们把方法可能会抛出的异常告知使用此方法的客户端程序员,它使得调用者能确切知道写什么样的代码可以捕获所有潜在的异常。

Java使用了一种语法,使你能以礼貌的方式告知客户端程序员某个方法可能会抛出异常 ,然后客户端程序员就可以进行相应的处理。这样异常说明。它属于方法声明的一部分,紧跟在形式参数列表之后。

练习8

/* Write a class with a method that throws an exception of the type created
 * in Exercise 4. Try compiling it without an exception specification to see
 * what the compiler says. Add the appropriate exception specification. Try
 * out your class and its exception inside a try-catch clause.
 */


class Test8 {
    public static void f() throws Exception4 {
        System.out.println("f()");
        throw new Exception4("Ouch from f()");
    }
}

public class No8Ex {
    public static void main(String[] args) {
        try {
            Test8 t = new Test8();
            t.f();
        } catch(Exception4 e) {
            System.err.println("Caught Exception4");
            e.printStackTrace();
            e.showS();
        }
    }
}
============================================================
f()
Exception4()
Message from Exception4: Ouch from f()
Caught Exception4
第十二章异常处理.Exception4: Ouch from f()
	at 第十二章异常处理.Test8.f(No8Ex.java:13)
	at 第十二章异常处理.No8Ex.main(No8Ex.java:21)

12.6 捕获所有异常

捕获异常的基类Exception

放在处理程序列表的末尾,以防它抢在其他处理程序之前先把异常捕获了

Exception没有太多的具体信息,可以调用它的基类Throwable继承方法:

String getMessage()

String getLocalizeMessage()

来获取详细信息,或用本地语言表示详细信息。

String toString()

返回对Throwable的简单描述,要是有详细信息的话,也会把它包含在内:

void printStrackTree() 输出到标准错误

void printStackTree(PrintStream) 选择PrintStream为输出流

void printStackTree(java.io.PrintWriter) 选择Jav.io.PrintWriter 为输出流

打印Throwable和Throwable的调用栈轨迹”把你带到异常抛出点“的方法调用序列

练习9

/* Create three new types of exceptions. Write a class with a method that
 * throws all three. In main(), call the method but only use a single catch
 * clause that will catch all three types of exceptions.
 */

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

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

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

public class No9Ex {
    public static void f(int x) throws ExceptionA, ExceptionB, ExceptionC {
        if(x < 0) throw new ExceptionA("x < 0");
        if(x == 0) throw new ExceptionB("x == 0");
        if(x > 0) throw new ExceptionC("x > 0");
    }
    public static void main(String[] args) {
        try {
           // f(0);
            f(1);
            f(-1);
            // will catch any Exception type:
        } catch(Exception e) {
            System.out.println("Caught Exception");
            e.printStackTrace(System.out);
        }
    }
}
====================================================================Caught Exception
第十二章异常处理.ExceptionC: x > 0
	at 第十二章异常处理.No9Ex.f(No9Ex.java:24)
	at 第十二章异常处理.No9Ex.main(No9Ex.java:29)

12.6.1 栈轨迹

printStrackTrace()方法提供的信息可通过 getStackTree()方法来直接访问,这个方法返回一个由栈轨迹中的元素所构成的数组。其中每一个元素表示栈的一帧,元素0表示栈顶元素,调用序列中的最后一个方法调用,最后一个元素是调用序列中的第一个方法调用。

12.6.1 重新抛出异常

重抛异常会把异常抛给上一级环境中的异常处理程序,PrintStackTrace()方法显示的将原来异常抛出点的调用栈西悉尼,而不是重新抛出点的信息。

使用fillInStackTrace(),返回一个Throwable对象,当前调用栈信息填入原来那个异常对象而建立的,异常新发地,有关原来异常发生点地信息丢失,剩下地是与新的抛出点有关的信息

不必对异常对象地清理而担心,它们都是new在堆上创建地对象,垃圾回收期会自动把它们清理掉

12.6.3 异常链

想在捕获一个异常后抛出另一个异常,并且希望把原始地异常地信息保存下来,被称为异常链

所有的Throwable都可以接受一个cause对象作为参数,cause用来表示原始异常,这样传递给新异常。

三种基本异常类提供了带cause的参数构造器:

  • Error java 虚拟机报告系统错误
  • Exception
  • RuntimeException

如果要把其他类型连接起来,应该使用initCause()方法

练习10

/* Create a class with two methods, f() and g(). In g(), throw an exception of
 * a new type that you define. In f(), call g(), catch its exception and, in the
 * catch clause, throw a different exception (of a second type that you define).
 * Test your code in main().
 */

class GException extends Exception {
    GException(String s) { super(s); }
}

class HException extends Exception {
    HException(String s) { super(s); }
}

public class No10Ex {
    static void f() {
        try {
            try {
                g();
            } catch(GException ge) {
                System.out.println("Caught GException in f inner try");
                ge.printStackTrace();
                throw new HException("from f(), inner try");
            }
        } catch(HException he) {
            System.out.println("Caught HException in f() outer try");
            he.printStackTrace(System.out);
        }
    }
    static void g() throws GException {
        throw new GException("from g()");
    }
    public static void main(String[] args) {
        f();
    }
}
======================================================================
    Caught GException in f inner try
Caught HException in f() outer try
第十二章异常处理.HException: from f(), inner try
	at 第十二章异常处理.No10Ex.f(No10Ex.java:25)
	at 第十二章异常处理.No10Ex.main(No10Ex.java:36)
第十二章异常处理.GException: from g()
	at 第十二章异常处理.No10Ex.g(No10Ex.java:33)
	at 第十二章异常处理.No10Ex.f(No10Ex.java:21)
	at 第十二章异常处理.No10Ex.main(No10Ex.java:36)

练习11

// TIJ4 Chapter Exceptions, Exercise 11, page 468
/* Repeat the previous exercise, but inside the catch clause, wrap g()'s
 * exception in a RuntimeException.
 */


public class No11Ex {
    static void f() {
        try {
            g();
        } catch(GException ge) {
            System.out.println("Caught GException in f try");
            ge.printStackTrace();
            throw new RuntimeException(ge);
        }
    }
    static void g() throws GException {
        throw new GException("from g()");
    }
    public static void main(String[] args) {
        f();
    }
}
==============================================================
    Caught GException in f try
第十二章异常处理.GException: from g()
	at 第十二章异常处理.No11Ex.g(No11Ex.java:20)
	at 第十二章异常处理.No11Ex.f(No11Ex.java:12)
	at 第十二章异常处理.No11Ex.main(No11Ex.java:23)
Exception in thread "main" java.lang.RuntimeException: 第十二章异常处理.GException: from g()
	at 第十二章异常处理.No11Ex.f(No11Ex.java:16)
	at 第十二章异常处理.No11Ex.main(No11Ex.java:23)
Caused by: 第十二章异常处理.GException: from g()
	at 第十二章异常处理.No11Ex.g(No11Ex.java:20)
	at 第十二章异常处理.No11Ex.f(No11Ex.java:12)
	... 1 more

12.7 Java标准异常

​ Throwable这个Java类被用来表示任何可以作为异常被抛出的类。

两种类型:

  • Error用来表示编译时和系统错误
  • Exception 而可以被抛出的基本类型,Java类库,用户方法以及运行时故障都可能抛出Exception型异常。

12.7.1 特例:RuntimeException

Java的标准运行时会检查传递给方法的每个引用其是否为null,所以你不必对每个传递给方法的每个引用都检查其是否为null

运行时异常类型有很多,它们会自动被Java虚拟机抛出,所以不必再异常说明中把它们列出来。这些异常都是RuntimeException继承过来的,”不受检查异常“。这种异常属于错误,将被自动捕获,就不用你亲自动手了。

只能在代码中忽略RuntimeException(及其子类)类型的异常,其他类型异常的处理都是由编译器强制实施的。究其原因,RuntimeException代表的是编程错误:

  • 无法预料的错误,你从控制范围之外传递进来的null引用
  • 在代码进行中检查的错误。

异常被设计

  • 处理一些烦人的运行时错误,代码控制能力之外的因素导致
  • 发现某些编辑器无法检测道德编程错误

12.8 使用finally进行清理

​ 无论try块中的异常是否抛出,他都能得到执行。这通常适用于内存回收之外的情况

12.8.1 finally用来做什么

对于没有垃圾回收和 析构函数(当对象不再被使用的时候调用的函数C++)的语言来说,finally非常重要。它是程序员保证:无论try块里发生了什么,内存总能得到释放。Java什么情况下才能用的finally呢?

当要把除内存资源之外的资源恢复到它们的初始状态时,就要用到finally语句

  • 已经打开的文件或者网络连接
  • 在屏幕上画图形
  • 外部世界的某个开关

在异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高的一层的异常处理程序之前,执行finally语句,当涉及break和continue语句的时候,finally语句也会得到执行。

练习13

/* Modify Exercise 9 by adding a finally clause. Verify that your
 * finally clause is executed, even if a NullPointerException is thrown.
 */


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

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

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

public class No13Ex {
    // array element will be initialized to null:
    private static Integer[] x = new Integer[1];
    public static void f(int x)
            throws ExceptionA, ExceptionB, ExceptionC {
        if(x < 0) throw new ExceptionA("x < 0");
        if(x == 0) throw new ExceptionB("x == 0");
        if(x > 0) throw new ExceptionC("x > 0");
    }
    public static void main(String[] args) {
        try {
            // to throw NullPointerException:
            f(x[0]);
            f(0);
            f(1);
            f(-1);
            // will catch any Exception type:
        } catch(Exception e) {
            System.out.println("Caught Exception");
            e.printStackTrace(System.out);
        } finally {
            System.out.println("made it to finally");
        }
    }
}
=========================================================================
Caught Exception
第十二章异常处理.ExceptionB: x == 0
	at 第十二章异常处理.No13Ex.f(No13Ex.java:26)
	at 第十二章异常处理.No13Ex.main(No13Ex.java:33)
made it to finally

练习14

// Show that the OnOffSwitch.java can fail by throwing a
// RuntimeException inside the try block.

public class OnOffSwitch14 {
    private static Switch sw = new Switch();
    static Integer[] x = new Integer[1];
    public static void f(int i)
            throws OnOffException1, OnOffException2 {}
    public static void main(String[] args) {
        try {
            sw.on();
            // Code that can throw RuntimeException
            // and leave Switch on:
            f(x[0]);
            sw.off();
        } catch(OnOffException1 e) {
            System.out.println("OnOffException1");
            sw.off();
        } catch(OnOffException2 e) {
            System.out.println("OnOffException2");
            sw.off();
        }
    }
}

练习15

// Show that WithFinally.java doesn't fail by throwing a 
// RuntimeException inside the try block.

public class WithFinally15 {
	private static Switch sw = new Switch();
	// set up x[0] = null:
	private static Integer[] x = new Integer[1];
	public static void f(int i) 
	throws OnOffException1, OnOffException2 {}
	public static void main(String[] args) {
		try {
			sw.on();
			// Code to throw NullPointerException: 
			f(x[0]);
		} catch(OnOffException1 e) {
			System.out.println("OnOffException1");
		} catch(OnOffException2 e) {
			System.out.println("OnOffException2");
		} finally {
			sw.off();
		} 
	}		
} 

12.8.2 在return中使用finally

因为finally子句总归是会执行的,所以在一个方法中,可以多个点返回,并且可以保证重要的清理工作仍旧会执行 。

18.3.3 缺憾:异常丢失

Java的异常实现也有瑕疵。程序出错的标志,

用某些特殊的final子句,就会发生这种情况

  • 里层没有catch里层try语句直接final
  • final子句return 即使抛出了异常,不会产生任何输出

练习18

// Add a second level of exception loss to LostMessage.java so that the
// HoHumException is itself replaced by a third exception.

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

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

class MeaninglessException extends Exception {
    public String toString() {
        return "A meaningless exception";
    }
}

public class No18Ex {
    void f() throws VeryImportantException {
        throw new VeryImportantException();
    }
    void dispose() throws HoHumException {
        throw new HoHumException();
    }
    void eliminate() throws MeaninglessException {
        throw new MeaninglessException();
    }
    public static void main(String[] args) {
        try {
            No18Ex lm = new No18Ex();
            try {
                try {
                    lm.f();
                    lm.dispose();
                } finally {
                    lm.eliminate();
                }
            } catch(Exception e) {
                System.out.println(e);
            }
        } catch(Exception e) {
            System.out.println(e);
        }
    }
}
==========================================================
A meaningless exception

练习19

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);
		}
	}
}
A very important exception!
A trivial exception

12.9 异常的限制

当覆盖方法的时候,只能抛出在其基类方法的异常说明里列出的那些异常。

这意味着当基类使用的代码应用到其派生类对象的时候,一样能够工作(面向对象的基本概念):

  • 方法声明将抛出异常,但实际上没有抛出,这种方式使你能强制用户去捕获可能在覆盖后的event()版本中增加的异常,所以它很合理。这对于抽象方法同样成立。

  • 如果一个类继承了基类和实现了一个接口,那么接口不能向基类中现有的方法增加异常,或改变异常

  • 如果接口里定义的方法不是来自基类,那么方法抛出什么样的异常也都没有问题。或者实现现有方法,但可以不抛出异常

  • 对构造器没有限制,继承类的构造器可以抛出任何异常,而不必理会基类构造所抛出的异常,但是必须包含基类构造器异常

  • 派生类构造器不能捕获基类构造器异常

  • 通过强制派生类遵守基类方法的异常说明,对象的可替换性得到了保证

  • 可以向上转型为基类,编译器就会要求你捕获基类的异常,额能产生更强制的异常类代码

继承过程中,编译器会对异常进行强制要求,但异常说明本身并不属于方法类型的一部分,不基于异常说明来重载方法。一个出现在基类方法的异常说明的异常,不一定出现在派生类方法的异常说明里。在继承和覆盖的过程中,基类的方法必须出现在派生类里,”某个特定方法的异常说明接口不是变大而是变小的,与接口和类在继承时情形相反 “

练习20

/* MOdify StormyInning.java by adding an UmpireArgument exception type
 * and methods that throw this exception. Test the modified hierarchy.
 */

// Overridden methods may throw only the exceptions
// specified in their base-class versions, or exceptions
// derived from the base-class exceptions.

import 第十章内部类.No20;

class BaseballException extends Exception {}
class Foul extends BaseballException {}
class Strike extends BaseballException {}
class UmpireArgument extends BaseballException {}
class ThrownFromGame extends UmpireArgument {}

abstract class Inning {
    public Inning() throws BaseballException {}
    public void event() throws BaseballException {
        // Doesn't actually have to throw anything
    }
    public abstract void atBat() throws Strike, Foul, UmpireArgument;
    public void questionableCall() throws UmpireArgument {}
    public void walk() {} // Throws no checked exceptions
}

class StormException extends Exception {}
class RainedOut extends StormException {}
class PopFoul extends Foul {}


interface Storm {
    public void event() throws RainedOut;
    public void rainHard() throws RainedOut;
}

public class No20StormyInning extends Inning implements Storm {
    // OK to add new exceptions for constructors, but you
    // must deal with the base constructor exceptions:
    public No20StormyInning()
            throws UmpireArgument, RainedOut, BaseballException {}
    public No20StormyInning(String s)
            throws Foul, BaseballException {}
    // Regular methods must comform to base class:
    //! void walk() throws PopFoul {} // Compile error
    // Interface CANNOT add exceptions to existing
    // methods from the base class:
    //! public void event() throws RainedOut {}
    // If method doesn't already exist in the
    // base class, the exception is OK:
    public void rainHard() throws RainedOut {}
    // You can choose to not throw any exceptions,
    // even if the base class version does:
    public void event() {}
    // Overridden methods can throw inherited exceptions:
    public void atBat() throws PopFoul, ThrownFromGame {
        throw new ThrownFromGame();
    }
    public void questionableCall() throws UmpireArgument {
        throw new UmpireArgument();
    }
    public static void main(String[] args) {
        try {
            No20StormyInning si = new No20StormyInning();
            si.atBat();
            si.questionableCall();
        } catch(PopFoul e) {
            System.out.println("Pop foul");
        } catch(UmpireArgument e) {
            System.out.println("Umpire argument (StormyInning20)");
            // } catch(ThrownFromGame e) {
            // System.out.println("Thrown from game");
        } catch(RainedOut e) {
            System.out.println("Rained out");
        } catch(BaseballException e) {
            System.out.println("Generic baseball exception");
        }
        // Strike not thrown in derived version.
        try {
            // What happens if you upcast?
            Inning i = new No20StormyInning();
            i.atBat();
            // You must catch the exceptions from
            // the base-class version of the method:
        } catch(Strike e) {
            System.out.println("Strike");
        } catch(Foul e) {
            System.out.println("Foul");
        } catch(ThrownFromGame e) {
            System.out.println("Thrown from game (Inning)");
        } catch(RainedOut e) {
            System.out.println("Rained out");
        } catch(BaseballException e) {
            System.out.println("Generic baseball exception");
        }
    }
}
========================================================================
 Umpire argument (StormyInning20)
Thrown from game (Inning)

12.10 构造器

如果异常发生了,所有的东西能被正确的清理吗?大多数情况时非常安全的,但涉及构造器时,问题就出现了,构造器会把对象设置成安全的初始状态。编写构造器时要格外小心。如果构造器抛出异常,就不能很好的初始化,导致清理行为也可能不能正常工作。

处理那些具有可失败的构造器,且需要清理的对象。为了正确处理这种情况,对于每个构造器,都必须包含在其自己的try-finally语句中,并且每个对象构造必须都跟随一个try-fianlly语句块以确保清理。

练习21

// Demonstrate that a derived-class constructor cannot catch exceptions thrown
// by its base-class constructor.

class BaseException extends Exception {}

class Base {
    Base() throws BaseException {
        throw new BaseException();
    }
}

class Derived extends Base {
    // BaseException must be caught (no way) or
    // declared to be thrown:
    Derived() throws BaseException {
       super();
        // not this way, 'catch' without 'try' not allowed:
        // catch(BaseException e) {}
        // not this way either, because call to super
        // must be first statement in constructor:
        // try {
        // super();
        // } catch(BaseException e) {}
    }
}

public class No21Derived {
    public static void main(String[] args) {
        try {
            Derived d = new Derived();
        } catch(BaseException e) {
            System.out.println("BaseException caught in main()");
        }

    }
}
========================================================================
BaseException caught in main()

练习22

/* Create a class called FailingConstructor with a constructor that might fail
 * partway through the construction process and throw an exception. In main(),
 * write code that properly guards against this failure.
 */

public class No22Ex {
    Integer[] ia = new Integer[2];
    String s;
    No22Ex(String s) throws Exception {
        ia[0] = 0;
        ia[1] = 1;
        ia[2] = 2;
        this.s = s;
    }
    public static void main(String[] args) {
        try {
            No22Ex fc = new No22Ex("hi");
        } catch(Exception e) {
            System.err.println("Caught Exception in main()");
            e.printStackTrace(System.err);
        } finally {

        }
    }
}
===================================================================
    Caught Exception in main()
java.lang.ArrayIndexOutOfBoundsException: 2
	at 第十二章异常处理.No22Ex.<init>(No22Ex.java:13)
	at 第十二章异常处理.No22Ex.main(No22Ex.java:18)

练习23

/* Add a class with a dispose() method to the previous exercise. Modify
 * FailingConstructor so that the constructor creates one of these disposable
 * objects, after which the constructor might through an exception, after which
 * it creates a second disposable member object. Write code to properly guard
 * against failure, and in main() verify that all possible failure situations
 * are covered.
 */
// This solution satisfies the conditions called for in FailingConstructor23
// constructor, which catches its own exceptions.

class Disposable {
    private static int counter = 0;
    private int id = counter++;
    private boolean disposed;
    Disposable() {
        disposed = false;
    }
    void dispose() {
        disposed = true;
    }
    String checkStatus() {
        return (id + " " + (disposed ? "disposed" : "not disposed"));
    }
}

public class No22FailingConstructor {
    private Integer[] ia = new Integer[2];
    private static Disposable d0;
    private static Disposable d1;
    No22FailingConstructor() throws Exception {
        try {
            d0 = new Disposable();
            try {
                ia[2] = 2;     // causes exception thrown and
                // caught in middle try loop
                try {
                    d1 = new Disposable();
                } catch(Exception e) {
                    System.out.println("Caught e in inner try loop");
                    e.printStackTrace(System.err);
                    System.out.println("Failed to create d1");
                }
            } catch(Exception e) {
                System.out.println("Caught e in middle try loop");
                e.printStackTrace(System.err);
                System.out.println("Disposing d0");
                d0.dispose(); // d0 would have been created
            }
        } catch(Exception e) {
            System.out.println("Caught e in outer try loop");
            e.printStackTrace(System.err);
            System.out.println("Failed to create d0");
        }

    }
    public static void main(String[] args) {
        try {
            // the constructor catches its own exceptions:
            No22FailingConstructor fc = new No22FailingConstructor();
        } catch(Exception e) {
            System.err.println("Caught Exception in main()");
            e.printStackTrace(System.err);
        }
    }
}
=========================================================
Caught e in middle try loop
Disposing d0
java.lang.ArrayIndexOutOfBoundsException: 2
	at 第十二章异常处理.No22FailingConstructor.<init>(No22FailingConstructor.java:36)
	at 第十二章异常处理.No22FailingConstructor.main(No22FailingConstructor.java:61)

练习24

/* Add a dipsose() method to the FailingConstructor class and write code to properly use
 * this class.
 */
// Solution modeled from examples in text:
import java.io.*;

public class No24Ex {
    private BufferedReader in;
    public No24Ex(String fname) throws Exception {
        try {
            in = new BufferedReader(new FileReader(fname));
        } catch(FileNotFoundException e) {
            System.out.println("Could not find file " + fname);
            throw e;
        } catch(Exception e) {
            try {
                in.close();
            } catch(IOException e2) {
                System.out.println("in.close() failed");
            }
            throw e;
        }
    }
    public String getLine() {
        String s;
        try {
            s = in.readLine();
        } catch(IOException e) {
            throw new RuntimeException("readLine() failed");
        }
        return s;
    }
    public void dispose() {
        try {
            in.close();
            System.out.println("dispose() successful");
        } catch(IOException e2) {
            throw new RuntimeException("in.close() failed");
        }
    }
    public static void main(String[] args) {
        try {
            No24Ex fc =
                    new No24Ex("No22Ex.java");
            try {
                String s;
                int i = 1;
                while((s = fc.getLine()) != null) {
                    // code to print to new file:
                    // println(i + " " + s);
                    // i++;
                }
            } catch(Exception e) {
                System.out.println("Exception caught in main()");
                e.printStackTrace(System.err);
            } finally {
                fc.dispose();
            }
        } catch(Exception e) {
            System.out.println("FailingConstructor22b construction failed");
        }
    }
}
===================================================
    Could not find file No22Ex.java
FailingConstructor22b construction failed

12.11 异常匹配

抛出异常的时候,异常处理系统会按照代码书写顺序找出 最近 的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找

派生类的对象也可以匹配其基类的处理程序

不能把基类的 catch 子句放在最外面,把派生类的异常放在里面,这样派生类异常永远得不到执行

/* Create a three-level hierarchy of exceptions. Now create a
 * base-class A with a method that throws an exception at the base
 * of your hierarchy. Inherit B from A and override the method so
 * it throws an exception at level two of your hierarchy. Repeat by
 * inheriting class C from B. In main(), create a C and upcast it
 * to A, then call the method.
 */

class LevelOneException extends Exception {}
class LevelTwoException extends LevelOneException {}
class LevelThreeException extends LevelTwoException {}

class A {
    void f() throws LevelOneException {
        throw new LevelOneException();
    }
}

class B extends A {
    void f() throws LevelTwoException {
        throw new LevelTwoException();
    }
}

class C extends B {
    void f() throws LevelThreeException {
        throw new LevelThreeException();
    }
}

public class No25Ex {
    public static void main(String[] args) {
        A a = new C();
        try {
            a.f();
        }  catch(LevelThreeException e3) {
            System.out.println("Caught e3");
        }  catch(LevelTwoException e2) {
            System.out.println("Caught e2");
        }  catch(LevelOneException e1) {
            System.out.println("Caught e1");
        }
    }
}
=================================================================
Caught e3

12.12 其他可选方式

异常处理系统就像一个活门(trap door),使你能放弃程序的正常执行序列。

重要原则:只有在你知道如果处理的情况下才捕获异常

重要目标:错误处理的代码同错误发生的地点相分离

异常检查会强制你在可能还没准备好处理错误的时候被迫假设catch语句,导致吞噬则害错误(harmful is swallowed)Java是强静态语言, 编译时就做类型检查的语言。

研究 被检查的异常 及其并发症,采取什么方法来解决问题

Java是强类型语言(编译时就会做类型检查的语言),要对类型检查持怀疑态度。(所有模型都是错误的,但是都是有用的)

  • 不在于编译器是否会强制程序员去处理错误,而是要有一致的,使用异常来报告错误的模型
  • 不在于什么时候进行检查,而是一定要有类型检查,也就是说必须强制程序使用正确的类型,至于这种强制施加于编译时还是运行时,那到没关系

通过把异常传递给控制台,就不必在main()里写try catch子句了

把”被检查的异常“转换为”不坚持的异常“,可以把”被检查的异常“包装进RuntimeException里面。

把 被检查的异常 这种功能屏蔽

练习27

// Modify Exercise 3 to convert the exception to a Runtime Exception.

public class No27Ex {
    private static int[] ia = new int[2];
    public static void main(String[] args) {
        try {
            ia[2] = 3;
        } catch(ArrayIndexOutOfBoundsException e) { // convert to RuntimeException:
            throw new RuntimeException(e);
        }
    }
}

练习28

/* Modify Exercise 4 so that the custom exception class inherits from 
* RuntimeException, and show that the compiler allows you to leave
* out the try block.
*/

class Exception28 extends RuntimeException {
	private String msg;
	Exception28(String msg) {
		super(msg);
		System.out.println("Exception28()");
		this.msg = msg;
	}
	protected void showS() { 
		System.out.println("Message from Exception4: " + msg);
	}
}

public class Ex28 {
	public static void f() throws Exception28 {
		System.out.println("f()");
		throw new Exception28("Ouch from f()");
	}
	public static void main(String[] args) {
		f();	
	}	
}
==============================================================
    f()
Exception28()
Exception in thread "main" 第十二章异常处理.Exception28: Ouch from f()
	at 第十二章异常处理.No28Ex.f(No28Ex.java:23)
	at 第十二章异常处理.No28Ex.main(No28Ex.java:26)

练习29

/* Modify all the exception types in StormyInning.java so that they extend
* RuntimeException, and show that no exception specifications or try blocks
* are necessary. Remove the '//!' comments and show how the methods can be 
* compiled without specifications.
*/

class BaseballException extends RuntimeException {}
class Foul extends RuntimeException {}
class Strike extends RuntimeException {}

abstract class Inning {
	public Inning() {}
	public void event() {}
	public abstract void atBat();
	public void walk() {} 
}

class StormException extends RuntimeException {}
class RainedOut extends RuntimeException {}
class PopFoul extends RuntimeException {}

interface Storm {
	public void event();
	public void rainHard();
}

public class StormyInning29 extends Inning implements Storm {
	public StormyInning29() {}
	public StormyInning29(String s) {}
	public void walk() {}
	public void event() {}
	public void rainHard() {}
	public void atBat() {}
	public static void main(String[] args) {
		StormyInning29 si = new StormyInning29();
		si.atBat();
		// What happens if you upcast?
		Inning i = new StormyInning29();
		i.atBat();		
	}
}

练习30

/* Modify Human.java so that the exceptions inherit from
 * RuntimeException. Modify main() so that the technique
 * in TurnOffChecking.java is used to handle the different
 * types of exceptions.
 */

class Annoyance extends RuntimeException {}
class Sneeze extends Annoyance {}


class WrapCheckedExceptions {
    void throwRuntimeException(int type) {
        try {
            switch(type) {
                case(0):
                    throw new Annoyance();
                case(1):
                    throw new Sneeze();
                case(2):
                    throw new RuntimeException("Where am I?");
                default: return;
            }
        }  catch(Exception e) {
            // Adapt to unchecked:
            throw new RuntimeException(e);
        }
    }
}

public class No30Ex {
    public static void main(String[] args) {
        WrapCheckedExceptions wce =
                new WrapCheckedExceptions();
        for(int i = 0; i < 3; i++)
            try {
                if(i < 3)
                    wce.throwRuntimeException(i);
                else
                    throw new RuntimeException();
            }  catch(RuntimeException re) {
                try {
                    throw re.getCause();
                }  catch(Sneeze e) {
                    System.out.print("Sneeze: " + e);
                }  catch(Annoyance e) {
                    System.out.println("Annoyance: " + e);
                }  catch(Throwable e) {
                    System.out.println("Throwable: " + e);
                }
            }
    }
}
=====================================================================
    Annoyance: 第十二章异常处理.Annoyance
Sneeze: 第十二章异常处理.SneezeThrowable: java.lang.RuntimeException: Where am I?

12.13 异常使用指南

12.14 总结

如果不使用异常,那你只能完成很有限的工作。报告功能时异常的精髓所在,恢复可能不占10%

,恢复也只是展开异常栈。

posted @ 2021-02-19 13:55  AronJudge  阅读(36)  评论(0编辑  收藏  举报