[笔记] Java 异常相关

By | 12 11 月, 2017

Java 异常层次结构

Error 类的异常为 Java 运行时的系统内部错误导致,如果发生了这种异常,能通知给用户做做善后工作就已经不错了,也无力回天。所以这种异常在开发时基本是无需关心,也不能抛出的。
Exception 分支下的 RuntimeException 和 IOException 是按两种规则来划分的:

  • 由程序错误导致的异常属于 RuntimeException,像错误的类型转换、数组越界、空指针之类的
  • 程序本身没有问题,但由于像 I/O 错误这类问题导致的异常属于 IOException

如果出现了 RuntimeException,那么一定是你的问题。
Java 语言规范中,将所有派生于 Error 或 RuntimeException 类的异常称为 Unchecked Exception,所有其他异常称为 Checked Exception。

异常的抛出

throw new EOFException();

有一点需要注意,不允许在子类的 throws 说明符中出现超过超类方法所列出的异常类范围。

方法中的异常声明

一个方法,必须声明所有的 Checked Exception。其他异常,要么不可控制(Error),要么就应该避免发生(RuntimeException)。

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

不过语法上并没有禁止在方法中声明 Unchecked Exception,虽然这个习惯很不好。

异常类的创建

习惯上应该定义两个构造器,如下:

class CustomException extends IOException {
    public CustomException() {}
    public CustomException(String message) {
        super(message);
    }
}

异常捕获

如果调用了一个抛出 Checked Exception 的方法,就必须对它进行处理,或者将其继续传递。
通常情况下,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常继续传递。将异常直接交给能够胜任的处理器进行处理比压制它更好。

异常中信息的获取

  • 获取信息:e.getMessage()
  • 获取异常对象的实际类型:e.getClass().getName()

多个异常类的 catch

try {
    ...
} catch (FileNotFoundException | UnknownHostException e) {
    ...
}

这里的异常变量 e 是 final 变量,不能在子句体中为 e 赋不同的值。

异常的再次抛出

通常在 catch 子句中抛出异常是为了改变异常的类型,一般来说,用于对第三方系统的错误进行包装。
除去直接抛出新的异常之外,更好的实践是,将原始异常设置为新异常的原因,示例如下:

public static void main(String[] args) {
    try {
        exception();
    } catch (FileNotFoundException e) {
        System.out.printf("message: %sn", e.getMessage());
        Throwable origin = e.getCause();
        System.out.printf("origin class: %s, message: %sn",
            origin.getClass().getName(), origin.getMessage());
    }
}
public static void exception() throws FileNotFoundException {
    try {
        throw new EOFException("origin exception");
    } catch (EOFException e) {
        FileNotFoundException se =  new FileNotFoundException("new exception");
        se.initCause(e);
        throw se;
    }
}

运行结果为:

message: new exception
origin class: java.io.EOFException, message: origin exception

带资源的 try 语句

通常来说,对于携带资源的语句,可以使用 finally 语句来保证无论如何都将这个资源关闭。但是考虑下面的情况:

在 try 块中抛出了一个调用者需要处理的异常,但在最后执行 finally 中的语句时,close 方法又抛出了一个异常,那么原来需要被处理的异常就被覆盖掉了,调用者接收到的变成了 close 方法的异常。

如果还是用 finally 的方法去解决,代码会变的很繁琐,但是带资源的 try 语句可以很好的解决上面的问题。
假设资源属于一个实现了 AutoCloseable 接口的类,这个接口有一个方法:

void close() throws Exception

那么就可以使用如下的 try 语句:

try (Resource res = ...) {
}

当 try 退出时,将会自动调用 res.close()。如果 try 块抛出了异常,close 方法也抛出了一个异常,在新的方式下,原来的异常会重新抛出,而 close 方法的异常会“被抑制”。
当然如果也希望获取 close 中的那些异常,可以通过调用 getSuppressed 方法来拿到被抑制的异常列表。

异常的最佳实践

  • 异常处理不能代替简单的测试。因为异常处理要消耗很多性能,简单的测试要快得多
  • 适度的控制异常块的范围。既不要一个 try 块走到底,也不要一句话一个 try
  • 利用异常的层次结构。不要为逻辑错误抛出 Checked Exception,如果有更合适的转换对象,那么不要犹豫
  • 不要压制异常。墨菲定律,可能发生的一定会发生
  • 在检测错误的时候,苛刻比放任更好。比如因为无效参数,栈空了,那么抛出一个 EmptyStackException 会比以后某个地方抛出了 NullPoinerException 要好得多
  • 不要羞于传递异常。如果你不能在自己这里完美的解决,那么传递出去,让上层解决,也许告知用户或放弃命令会更加合适

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注