摘自 《Java 核心技术(卷 Ⅰ)》

《Java 核心技术(卷 Ⅰ)》
《Java 核心技术(卷 Ⅰ)》

异常、断言和日志

1. 处理错误

  1. 用户输入错误
  2. 设备错误
  3. 物理限制
  4. 代码错误

1.1 异常分类

在 Java 中,异常对象都是派生于Throwable的一个实例

Java 中的异常层次结构
Java 中的异常层次结构

需要注意的是,所有的异常都是由Throwable继承而来,但在下一层立即分解为两个分支ErrorException

Error类层次结构描述了 Java 运行时系统的内部错误资源耗尽错误。应用程序不应该抛出这种类型的对象。如果出现了这样的内部情况,只能通告给用户,并尽力使程序安全的终止,再无能为力了。

Exception类层次结构又可以分解为两个分支:一个分支派生于RuntimeException,另一个分支包含其他异常,划分规则是:由程序错误导致的异常属于RuntimeException,而程序本身没有问题,但由于I/O错误这类问题导致的异常属于其他异常

1.2 声明受查异常

对于那些可能被他人使用的 Java 方法,应该根据异常规范(exception specification),在方法的首部声明这个方法可能抛出的异常

class MyAnimation {
    ...
    public Image loadImage(String s) throws IOException {
        ...
    }
}

如果有可能抛出多个受查异常类型,那么就必须在方法的首部列出所有的异常类,每个异常类之间用逗号隔开:

class MyAnimation {
    ...
    public Image loadImage(String s) throws FileNotFoundException, EOFException {
        ...
    }
}

但是,不需要声明 Java 的内部错误,即Error继承的错误。任何程序代码都具有抛出那些异常的可能,而我们对其也没有任何控制能力

1.3 如何抛出异常

throw new EOFException();
// 或者
EOFException e = new EOFException();
throw e;

异常类中抛出异常:

String readData(Scanner in) throws EOFException {
    ...
    while (...) {
        if (!in.hasNext()) { // EOF encountered
            if (n < len)
                throw new EOFException();
        }
        ...
    }
    return s;
}

1.4 创建异常类

class FileFormatException extends IOException {
    public FileFormatException() {}
    public FileFormatException(String gripe) {
        super(gripe);
    }
}

2. 捕获异常

2.1 如何捕获异常

如果某个异常发生的时候没有在任何地方进行捕获,那么程序就会终止执行,并在控制台上打印出异常信息,其中包括异常的类型堆栈的内容

try {
    code
    more code
} catch (Exception e) {
    handler for this type
}

如果在try语句块中的任何代码抛出了一个在catch子句中说明的异常类,那么:

  1. 程序将跳过try语句块的其余代码
  2. 程序将执行catch子句中的handler代码

如果在try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句。

如果方法中的任何代码抛出了一个在catch子句中没有声明的异常类型,那么这个方法就会立刻退出

public void read(String filename) {
    try {
        InputStream in = new FileInputStream(filename);
        int b;
        while ((b = in.read()) != -1) {
            // process input
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

还可以什么都不做,将异常传递给调用者,这样就必须声明这个方法可能会抛出一个IOException

public void read(String filename) throws IOException {
    InputStream in = new FileInputStream(filename);
    int b;
    while ((b = in.read()) != -1) {
        // process input
    }
}

Java 编译器严格的执行throws说明符。如果调用了一个抛出受查异常的方法,就必须对它进行处理,或者继续传递

2.2 捕获多个异常

在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理

try {
    // some code
} catch (FileNotFoundException e) {
    // handle exception
} catch (UnknownHostException e) {
    // handle exception
} catch (IOException e) {
    // handle exception
}

还可以通过以下语句获得对象的更多信息

e.getMessage();
e.getClass().getName();

Java SE 7 中,同一个catch子句中可以合并多个异常类型

try {
    // some code
} catch (FileNotFoundException | UnknownHostException e) {
    // handle exception
} catch (IOException e) {
    // handle exception
}

2.3 再次抛出异常与异常链