73. 抛出与抽象对应的异常

  如果方法抛出的异常与它所执行的任务没有明显的联系,这种情形将会使人不知所措。当方法传递由低层抽象抛出的异常时,往往会发生这种情况。除了使人感到困惑之外,这也“污染”了具有实现细节的更高层的 API 。如果高层的实现在后续的发行版本中发生了变化,它所抛出的异常也可能会跟着发生变化,从而潜在地破坏现有的客户端程序。

  为了避免这个问题, 更高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常。这种做法称为异常转译 (exception translation),如下代码所示:

  1. /* Exception Translation */
  2. try {
  3. ... /* Use lower-level abstraction to do our bidding */
  4. } catch (LowerLevelException e) {
  5. throw new HigherLevelException(...);
  6. }

  下面的异常转译例子取自于 AbstractSequentialList 类,该类是 List 接口的一个骨架实现(skeletal implementation),详见第 20 条。在这个例子中,按照List<E>接口中 get 方法的规范要求,异常转译是必需的:

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

  一种特殊的异常转译形式称为异常链(exception chaining),如果低层的异常对于调试导致高层异常的问题非常有帮助,使用异常链就很合适。低层的异常(原因)被传到高层的异常,高层的异常提供访问方法 (Throwable 的 getCause 方法)来获得低层的异常:

  1. // Exception Chaining
  2. try {
  3. ... // Use lower-level abstraction to do our bidding
  4. } catch (LowerLevelException cause) {
  5. throw new HigherLevelException(cause);
  6. }

  高层异常的构造器将原因传到支持链(chaining-aware)的超级构造器,因此它最终将被传给 Throwable 的其中一个运行异常链的构造器,例如 Throwable(Throwable) :

  1. /* Exception with chaining-aware constructor */
  2. class HigherLevelException extends Exception {
  3. HigherLevelException( Throwable cause ) {
  4. super(cause);
  5. }
  6. }

  大多数标准的异常都有支持链的构造器。对于没有支持链的异常,可以利用 Throwable 的 initCause 方法设置原因。异常链不仅让你可以通过程序(用 getCause)访问原因,还可以将原因的堆战轨迹集成到更高层的异常中。

  尽管异常转译与不加选择地从低层传递异常的做法相比有所改进,但是也不能滥用它。 如有可能,处理来自低层异常的最好做法是,在调用低层方法之前确保它们会成功执行,从而避免它们抛出异常。有时候,可以在给低层传递参数之前,检查更高层方法的参数的有效性,从而避免低层方法抛出异常。

  如果无法阻止来自低层的异常,其次的做法是,让更高层来悄悄地处理这些异常,从而将高层方法的调用者与低层的问题隔离开来。在这种情况下,可以用某种适当的记录机制(如 java.util.logging)将异常记录下来。这样有助于管理员调查问题,同时又将客户端代码和最终用户与问题隔离开来。

  总而言之,如果不能阻止或者处理来自更低层的异常,一般的做法是使用异常转译,只有在低层方法的规范碰巧可以保证“它所抛出的所有异常对于更高层也是合适的”情况下,才可以将异常从低层传播到高层。异常链对高层和低层异常都提供了最佳的功能:它允许抛出适当的高层异常,同时又能捕获低层的原因进行失败分析(详见第 75 条) 。