高效Java:抛出适合抽象的异常 - Kyle Carter


《Effective Java》一书的大部分内容都是聚焦构建一个干净、易懂的 API 以及它如何成为一个伟大库的基础。类的 API 的一部分是任何异常,它可能会抛出已检查(它成为签名的一部分)或未检查的堆栈。作为代码的编写者,我们有责任确保此 API 不会出现任何意外或令人震惊的情况。发生这种情况的一种方式是暴露一个对我们正在编写的类没有意义的异常。
不匹配异常的一个潜在示例是,如果您请求将两个数字相加时,方法抛出一个IOException. 通过抛出这个低级异常,你将实现细节暴露给调用者,实现细节在未来可能会改变,但现在它是你的 API 的一部分,因此很难改变。那么有什么方法可以解决这个问题呢?
用于解决此问题的主要方法是执行所谓的异常转换。异常转换是当您捕获较低级别的异常并将其包装在与您正在处理的抽象相匹配的较高级别的异常中时。

public E get(int index) {
  ListIterator<E> = listIterator(index);
  try {
    return i.next();
  } catch (NoSuchElementException e) {
    throw new IndexOutOfBoundsException("Index: " + index);
  }
}

这里将
NoSuchElementException 
异常转为IndexOutOfBoundsException抛出。
 
上面是将较低级别的异常包装在较高级别的异常中,但也将较低级别的异常作为原因传递给较高级别的异常。许多方法公开此原因字段,并将其传递给 Throwable 类。
更重要的是,这个原因是通过堆栈跟踪暴露出来的,这可以极大地帮助调试问题。这确实间接地向调用方法公开了较低级别的详细信息。然而,它并没有很直接地完成它,因此它不会强制调用者处理低级异常,相反,他们仍然可以处理高级异常,而不必担心低级实现细节。
 
最容易处理的异常是不会被抛出的异常。我们应该始终努力在我们的所有代码中不抛出可避免的异常。Effective Java有时甚至建议我们可以解决异常并简单地记录它们并继续前进。不过,我会警告不要使用这种模式。如果我们只是在特殊情况下抛出异常,那么调用者可能需要知道发生了一些事情,并且简单地将其隐藏在调用者面前可能会出现问题。