Throwables
Guava's Throwables 工具可以轻松的解决exceptions问题。
Propagation
有时,当你catch 到一个异常,但是你想将其抛给下一个try/catch块。
通常RuntimeException 或 Error实例不需要try/catch,但是如果你没有准备捕获它们时,会被try/catch所捕获。
Guava 提供一系列的工作使得程序能够简易的传播exceptions. 例如:
try {
someMethodThatCouldThrowAnything();
} catch (IKnowWhatToDoWithThisException e) {
handle(e);
} catch (Throwable t) {
Throwables.propagateIfInstanceOf(t, IOException.class); Throwables.propagateIfInstanceOf(t, SQLException.class);
throw Throwables.propagate(t);
}
这些方法通过自己抛出异常,但是抛出的结果-- e.g. throw Throwables.propagate(t) --可以被用来证明是编译期异常。
这里列举几个Guava提供的 propagation方法:
Signature | Explanation |
RuntimeException propagate(Throwable) | Propagates 可以抛出RuntimeException 或者 Error, 或者将异常封装在 RuntimeException ,否则抛出。为了确保抛出,将返回一个 RuntimeException ,因此可以这样写Throwables.propagate(t) , 同时 Java 将识别此异常是能够被向上抛出的。 |
void propagateIfInstanceOf(Throwable, Class<X extends Exception>) throws X | Propagates 将抛出一个是X的实例的异常。 |
void propagateIfPossible(Throwable) | 抛出throwable 如果是RuntimeException 或者 Error的话。 |
void propagateIfPossible(Throwable, Class<X extends Throwable>) throws X | 抛出throwable 如果是RuntimeException , Error或者X的话。 |
Uses for Throwables.propagate
模仿Java 7 的多重catch与 rethrow
显然,如果你想让一个异常传播到栈中是不需要catch块的,因此你不必覆盖此异常甚至不应该打印日志或者执行其他行为。最后的清理工作不管完成还是没有都要执行,因此会放在finally块中。然而,当你不得不更新错误数量再传播异常之前,当你想要有条件的传播异常,这时在一个catch块中的重抛出就显得极其重要了。
抓住与重抛出异常是
处理单一异常时,Catching 与 rethrowing是最直接的办法,但是在处理多种异常时会显得极其混乱。
@Override
public void run() {
try {
delegate.run();
} catch (RuntimeException e) {
failures.increment();
throw e;
} catch (Error e) {
failures.increment();
throw e;
}
}
Java 7 利用 multicatch解决问题:
} catch (RuntimeException | Error e) { failures.increment(); throw e; }
非Java 7用户更愿意向下面这样写,但是编译器禁止抛出一个类型为 Throwable的变量:
} catch (Throwable t) { failures.increment(); throw t; }
解决方案是将 throw t 用throw Throwables.propagate(t)代替。 在这种有限的情况下Throwables.propagate 的行为与原始代码是相同的。然而 However, it's easy to write code with 使用Throwables.propagate会时写代码变得很简单,因为其有着其他隐藏的行为。 特别注意的是上面的模式只对RuntimeException和Error起作用。如果想要catch块catch住检查时异常就需要调用propagateIfInstanceOf 去执行此行为,Throwables.propagate 是不能直接传播一个检查时异常的。
总体上说,这样使用propagate 的方式很一般。该方法需要在Java 7下,在其他版本下它节省了少量的复制,但也可以利用简单的提取方法重构替代。最后使用 propagate [让偶尔对检查时异常的封装更容易]
(https://github.com/google/guava/commit/287bc67cac97052b13cbbc0358aed8054b14bd4a).
没有必要将"throws Throwable"转换为 "throws Exception"
一些API,特别是Java反射API与JUnit会声明方法为andthrow Throwable. 与这些API交互是痛苦的,因为即使是最基础的API也只声明 throws Exception.。Throwables.propagate 被一些知道代码没有Exception,没有Error Throwable的调用者使用。 下面是一个声明一个调用JUnit测试的可调用的例子:
public Void call() throws Exception {
try {
FooTest.super.runTest();
} catch (Throwable t) {
Throwables.propagateIfPossible(t, Exception.class);
Throwables.propagate(t);
}
return null;
}
这里不需要propagate() , 因为第二行相当于 throw new RuntimeException(t). (这个例子也提醒了我, propagateIfPossible可能是令人费解的,因为它不仅传播给定类型的参数,而且传播Errors与 RuntimeExceptions。)
这种模式(或类似的变体,如抛出new RuntimeException(t))在谷歌的代码库中出现了30次。(Search for 'propagateIfPossible[^;]* Exception.class[)];'.) 它们中的大多数采用显式抛出new RuntimeException(t)方法。我们可能需要一个抛出式的throwWrappingWeirdThrowable方法来进行Throwable到Exception 的转换,但是考虑到有两种选择,除非我们还不赞成传播,否则可能没有太多的需要。
Throwables.propagate的有争议用途
争议: 将检查时异常转换为非检查时异常
原则上非检查异常代表着 bugs, 检查时异常代表着你不能控制的问题。实际上,JDK 有时甚至会[gets] (http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html#clone%28%29)
[it(http://docs.oracle.com/javase/6/docs/api/java/lang/Integer.html#parseInt%28java.lang.String%29)
[wrong] (http://docs.oracle.com/javase/6/docs/api/java/net/URI.html#URI%28java.lang.String%29) (至少对于一些方法而言是没有答案的 [no answer is right for everyone] (http://docs.oracle.com/javase/6/docs/api/java/net/URI.html#create%28java.lang.String%29)).
事实上,调用方有时必须在异常类型之间进行转换:
try {
return Integer.parseInt(userInput);
} catch (NumberFormatException e) {
throw new InvalidInputException(e);
}
try {
return publicInterfaceMethod.invoke();
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
有时,另一些调用者会使用 Throwables.propagate. 这样做有什么风险呢?
其中一个主要原因是代码的含义不太明显。什么是 throw Throwables.propagate(ioException) 的作用? 什么是 throw new RuntimeException(ioException) 的作用? 这两个做的是同一件事,只是后者更加直接。 前者的问题是: "这个方法干了什么?仅仅是封装了RuntimeException, ? 要是这样的话为什么要写一个方法去进行封装?"
不可否认,这里的一部分问题是“propagate”是一个模糊的名字。 (它是[一种抛出未声明异常的方法] (http://www.eishay.com/2011/11/throw-undeclared-checked-exception-in.html)?)可能"wrapIfChecked" 能更加代表其功能. 即使方法的名字叫准了,但是调用它来处理一个已经知道的检查时异常并没有什么优势。甚至还有其他劣势:可能会有更加适合的异常抛出,而不是抛出RuntimeException 。例如, IllegalArgumentException.
有时,当异常只可能是检查异常时,我们也会使用propagate 。 事实是这种选择更小更不直接。
} catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); }
} catch (Exception e) { throw Throwables.propagate(e); }
不管怎么样,一个明明存在但被人刻意的回避及无视的问题是( the elephant in the room) 将检查异常转换为未检查异常。 在某些情况下,这无疑是正确的,但它更频繁地用于避免处理合法的检查异常。 这就引出了一个关于检查异常是否是一般概念的争论。可以说Throwables.propagate 是不存在的,目的是鼓励 Java用户忽略 IOException 等。
Controversial: Exception tunneling
但是,当你实现一个不允许抛出异常的方法时呢? 有时需要在未检查的异常中包装异常。 这样做事对的,但propagate 是不需要对简单包装的。事实上,手动包装可能更好: 如果你想包装任何一个 exception (仅仅代替checked exceptions), 你可以不包装任何 exception来减少特殊情况。此外,您可能希望为包装使用自定义异常类型。
Controversial: Rethrowing exceptions from other threads
try {
return future.get();
} catch (ExecutionException e) {
throw Throwables.propagate(e.getCause());
}
这里有很多事情要考虑:
- 原因可能是发生了检查时异常。参阅上面的“将检查时异常转换为非检查时异常”。 但是如果作业知道抛出的不是检查时异常呢? (可能只是Runnable的结果) 如上所述,您可以捕获异常并抛出AssertionError;propagate很少提供。特别是Future ,也要考虑 Futures.get。
- 原因可能是没有Exception, 没有Error Throwable. (实际上,它不太可能是一个,但是如果你试图直接重排它,编译器会强迫你考虑这种可能性。) 参阅上面的“将检查时异常转换为非检查时异常”。
- 原因可能是非检查异常或者错误。如果是这样,将直接抛出。不幸的是,它的堆栈跟踪将反映最初创建异常的线程,而不是当前正在传播的线程。通常最好在异常链中包括线程堆栈跟踪,如在由get引发的ExecutionException 中。 (这个问题并不涉及propagate,而是关于在不同线程中重新抛出异常的任何代码。)
Causal Chain
Guava 使研究一个例外的因果链变得简单,提供了三种有用的方法,它们的名称是自解释的: