合理使用异常

阅读经典——《Effective Java》10

异常是面向对象编程中非常重要的一环,合理使用异常,可以提高程序的可读性、可靠性和可维护性。本文我们来讨论在Java中如何合理使用异常。

  1. 只针对异常的情况才使用异常
  1. 正确使用受检异常和运行时异常
  2. 优先使用标准的异常
  3. 异常转译和异常链

只针对异常的情况才使用异常

异常应该只用于异常的情况,而不是正常的控制流。任何试图使用异常来简化正常控制流的行为都是不值得提倡的。考虑下面的情况:

try {
  Iterator i = collection.iterator();
  while(true) {
    Foo foo  = i.next();
    ...
  }
} catch (NoSuchElementException e) {
}

使用异常检查迭代器是否遍历结束,这是很愚蠢的做法,可读性很差,而且在性能上可能更糟。Iterator作为一个设计良好的API,不可能鼓励用户这样使用,因此它提供了hasNext方法用来显式判断遍历是否结束。这也给API设计者一定的启发,不要让你的用户被迫使用异常来实现正常的控制流。

正确使用受检异常和运行时异常

Java程序设计语言提供了三种可抛出结构(throwable):受检异常(checked exception)、运行时异常(runtime exception)和错误(error)。

受检异常必须必须被try-catch块包围,用户有责任处理这个异常或继续向外抛出。这是对API用户的一种暗示:抛出异常是该方法的一种可能结果,请务必小心。

运行时异常用来表明编程错误,通常是不可恢复的。大多数运行时异常都是由于前提违例,前提违例是指API的用户没有遵守API规范建立的约定。例如,数组访问约定了下标值必须在0和数组长度减1之间,ArrayIndexOutOfBoundsException表明用户违反了这个前提。

错误往往被JVM保留用于表示资源不足、约束失败、或者其它使程序无法继续执行的条件。这是系统错误,我们不应该继承Error

自定义异常也是一个完整意义上的对象,因此我们可以在它上面定义各种方法,以提供额外的信息供用户查看。

优先使用标准的异常

Java类库为我们提供了一组基本的运行时异常,它们可以满足绝大多数API的异常抛出需求。我们应该尽量重用它们,而不是自定义全新的异常。因为这会使你的API更容易被用户理解,而且降低类加载的开销。常用的异常列表如下。

异常 使用场合
IllegalArgumentException 非null的参数值不正确
IllegalStateException 对于方法调用而言,对象状态不合适
NullPointerException 在禁止使用null的情况下参数值为null
IndexOutOfBoundsException 下标参数值越界
ConcurrentModificationException 在禁止并发修改的情况下,检测到对象的并发修改
UnsupportedOperationException 对象不支持用户请求的方法

异常转译和异常链

如果高层无法处理低层抛出的异常,就应该继续抛出。但抛出时建议使用异常转译:更高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常。

下面的异常转译例子取自AbstractSequentialList类,在这个例子中,按照List接口中get方法的规范要求,异常转译是必需的。使用该类的用户应该看到IndexOutOfBoundsException而不是NoSuchElementException

/**
 * Returns the element at the specified position in this list.
 * @throws IndexOutOfBoundsException if the index is out of range
 *         ({@code index < 0 || index >= size()}).
 */
public E get(int index) {
  ListIterator i = listIterator(index);
  try {
    return i.next();
  } catch (NoSuchElementException e) {
    throw new IndexOutOfBoundsException("Index:" + index);
  }
}

上面这种写法唯一的缺点是会丢失低层异常信息,不过我们可以用异常链来弥补这个缺点。我们的自定义异常应该提供一个接收Throwable参数的构造方法,把该参数传给父类的构造器,代码如下。这样用户使用我们的API时就可以用getCause访问到异常的底层原因。

// Exception with chaining-aware constructor
class HigherLevelException extends Exception {
  HigherLevelException(Throwable cause) {
    super(cause);
  }
}

关注作者或文集《Effective Java》,第一时间获取最新发布文章。

你可能感兴趣的:(合理使用异常)