TODO: rewrite with more examples
Guava's Throwables utility can frequently simplify dealing with exceptions.
Propagation 传播
Sometimes, when you catch an exception, you want to throw it back up to the next try/catch block. This is frequently the case forRuntimeException or Error instances, which do not require try/catch blocks, but can be caught by try/catch blocks when you don't mean them to.
有时候你想catch一个异常,你会把它throw到下一个try/catch块中.这常用于RuntiomeException或Error实例,他们不需要try/catch块,但是你想捕捉的时候也可以被try/catch块捕捉到.
Guava provides several utilities to simplify propagating exceptions. For example:
Guava提供了一些工具来简化传播异常.例如:
try{
someMethodThatCouldThrowAnything();
}catch(IKnowWhatToDoWithThisException e){
handle(e);
}catch(Throwable t){
Throwables.propagateIfInstanceOf(t,IOException.class);
Throwables.propagateIfInstanceOf(t,SQLException.class);
throwThrowables.propagate(t);
}
Each of these methods throw the exception themselves, but throwing the result -- e.g. throw Throwables.propagate(t) -- can be useful to prove to the compiler that an exception will be thrown.
每个方法都会抛出异常 -- 例如, throw Throwables.propagate(t) -- 对于提示编译器将会抛出一个异常是很有用的
Here are quick summaries of the propagation methods provided by Guava:
下面是一些Guava提供的异常传播方法的总结.
Signature | Explanation |
RuntimeException propagate(Throwable) |
Propagates the throwable as-is if it is a RuntimeException or an Error, or wraps it in a RuntimeException and throws it otherwise. Guaranteed to throw. The return type is a RuntimeException so you can write throw Throwables.propagate(t) as above, and Java will realize that that line is guaranteed to throw an exception. 将一个throwable作为一个RuntimeException或者Error传播出去, 或者将它包装成RuntimeException抛出.他肯定会抛出一个异常.他的返回值是一个RuntimeException, 所以你可以像上面那样使用throw Throwables.propagate(t), 而且Java会意识到这行代码肯定会抛出一个异常 |
void propagateIfInstanceOf(Throwable, Class<X extends Exception>) throws X |
Propagates the throwable as-is, if and only if it is an instance of X. 当Throwable是一个X的实例时将它传播出去 |
void propagateIfPossible(Throwable) |
Throws throwable as-is only if it is a RuntimeException or an Error. 当这个throwable是一个RuntimeException或者Error时,将其作为RuntimeException或Error抛出(也就是说如果这是一个checked exception将不会被抛出, 被吞掉了..) |
void propagateIfPossible(Throwable, Class<X extends Throwable>) throws X |
Throws throwable as-is only if it is a RuntimeException, an Error, or an X. 当这个throwable是一个RuntimeException或者Error或X时,将其作为RuntimeException或Error或X抛出 |
Uses for Throwables.propagate
Emulating Java 7 multi-catch and rethrow
模拟 Java 7 的多重捕捉(multi-catch)和重抛出(rethrow)
Typically, if a caller wants to let an exception propagate up the stack, he doesn't need a catch block at all. Since he's not going to recover from the exception, he probably shouldn't be logging it or taking other action. He may want to perform some cleanup, but usually that cleanup needs to happen regardless of whether the operation succeeded, so it ends up in a finally block. However, a catch block with a rethrow is sometimes useful: Maybe the caller must update a failure count before propagating an exception, or maybe he will propagate the exception only conditionally,
如果调用者先让异常传播到异常栈顶, 他就不需要使用catch块.因为它不打算修复这个异常,他可能不需要对这个异常做日志或做一些其他操作.他可能想做一些其他清理操作, 但是清理操作不管操作是否成功都是需要进行的,所以它需要将这些清理操作代码写在finally块里.然而.一个有rethrow的catch块是很有用的: 例如调用者需要在传播异常前更新一个失败次数的计数, 或者只有在某些情况下才传播这个异常
Catching and rethrowing an exception is straightforward when dealing with only one kind of exception. Where it gets messy is when dealing with multiple kinds of exceptions:
捕捉并重抛出一个异常在只处理一种类型的异常的时候是很清晰明了的. 但是在处理多种类型的异常会变得很混乱:
@Overridepublicvoid run(){
try{
delegate.run();
}catch(RuntimeException e){
failures.increment();
throw e;
}catch(Error e){
failures.increment();
throw e;
}
}
Java 7 solves this problem with multicatch:
Java 7 通过multicatch解决了这个问题
}catch(RuntimeException|Error e){
failures.increment();
throw e;
}
Non-Java 7 users are stuck. They'd like to write code like the following, but the compiler won't permit them to throw a variable of type Throwable:
非 Java 7 的用户会很纠结, 他们希望像下面这样写代码, 但是编译器是不会允许他们抛出一个Throwable类型的变量的(这里的意思是必须在方面签名写上throws Throwable, 而不能先RuntimeException和Error那样处理他)
}catch(Throwable t){
failures.increment();
throw t;
}
The solution is to replace throw t with throw Throwables.propagate(t). In this limited circumstance, Throwables.propagate behaves identically to the original code. However, it's easy to write code with Throwables.propagate that has other, hidden behavior. In particular, note that the above pattern works only with RuntimeException and Error. If the catch block may catch checked exceptions, you'll also need some calls to propagateIfInstanceOf in order to preserve behavior, as Throwables.propagate can't directly propagate a checked exception.
解决方案是使用Throwables.propagate(t)来代替throw t. 在这种局限的情况下,Throwables.propagate的表现和原来的代码是一样的.但是,当你有其他隐藏情况的时候, 使用Throwables.propagate来写代码会更简单. 特别的, 观察上面的模式, 他只有在异常是RuntimeException和Error的时候才能正常工作, 加入catch块捕捉到了checked exception, 你会需要调用propagateIfInstanceOf方法来保留行为(恢复这个Throwable真正的异常类型), 因为Throwables.propagate不能直接传播一个checked exception.
Overall, this use of propagate is so-so. It's unnecessary under Java 7. Under other versions, it saves a small amount of duplication, but so could a simple Extract Method refactoring. Additionally, use of propagate makes it easy to accidentally wrap checked exceptions.
总的来说, 这种propagate用法只是一般般了.在Java 7的时候你没必要这么写. 在其他版本时, 他确实可以帮你减少一点重复代码, 但使用一个简单的提炼函数来重构它同样可以做到.另外,使用propagate会很容易不小心就把checked exception包装成unchecked exception.
Unnecessary: Converting from throws Throwable to throws Exception
不必要的使用: 将throws Throwable 转换成 throws Exception
A few APIs, notably the Java reflection API and (as a result) JUnit, declare methods that throw Throwable. Interacting with these APIs can be a pain, as even the most general-purpose APIs typically only declare throws Exception. Throwables.propagate is used by some callers who know they have a non-Exception, non-Error Throwable. Here's an example of declaring a Callable that executes a JUnit test:
一些API, 特别是Java的反射API和Junit, 声明了一些会抛出Throwable的方法. 和这些方法打交道会很痛苦, 因为就算最多功能的API通常也只会声明throws Exception. Throwables.propagate被一些人用来处理一些他们知道是non-Exception, non-Error的Throwable. 这里有一个实现了Callable接口的方法执行Junit测试的例子:
publicVoid call()throwsException{
try{
FooTest.super.runTest();
}catch(Throwable t){
Throwables.propagateIfPossible(t,Exception.class);
Throwables.propagate(t);
}
returnnull;
}
There's no need for propagate() here, as the second line is equivalent to "throw new RuntimeException(t)." (Digression: This example also reminds me that propagateIfPossible is potentially confusing, since it propagates not just arguments of the given type but also Errors and RuntimeExceptions.)
这里没有必要使用propagete(), 因为第二行(Throwables.propagate(t))等同于"throw new RuntimeException(t)"(题外话: 这个例子让我想起propageteIfPossible有些潜在的困惑, 因为它传播了不仅是参数给定类型的异常, 还传播了Errors和RuntimeExceptions)
This pattern (or similar variants like "throw new RuntimeException(t)") shows up ~30 times in Google's codebase. (Search for'propagateIfPossible[^;]* Exception.class[)];'.) A slight majority of them take the explicit "throw new RuntimeException(t)" approach. It's possible that we would want a "throwWrappingWeirdThrowable" method for Throwable-to-Exception conversions, but given the two-line alternative, there's probably not much need unless we were to also deprecate propagateIfPossible.
这种模式(或者一个类似的变种"throw new RuntimeException(t)") 在Google的代码库里出现了大约30次(搜索一下 'propagateIfPossible[^;]* Exception.class[)];') 他们大多数都是使用显式的"throw new RuntimeException(t)"的方法.我们可能想要一个 "throw WrappingWeirdThrowable" 方法来进行 Throwable-to-Exception 转换, 但是用这两行代码来替代其实是没必要的,除非我们也反对(deprecate)propagateIfPossible (TODO 没看明白这句话)
Controversial uses for Throwables.propagate
对Throwables.propagte有争议的使用
Controversial: Converting checked exceptions to unchecked exceptions
争议1: 将checked exceptions 转为 unchecked exceptions
In principle, unchecked exceptions indicate bugs, and checked exceptions indicate problems outside your control. In practice, even the JDK sometimes gets it wrong (or at least, for some methods, no answer is right for everyone).
原则上讲, unchecked exception 代表 bug (程序员的主观失误), checked exceptions 代表你控制之外的问题(外部客观原因). 在实践上, 就算是JDK有时候也会把他们弄错(至少某些方法是这样的, 当然可能不同的人有不同的看法)
As a result, callers occasionally have to translate between exception types:
结果是调用者偶尔会需要在exception类型之间转换
try{
returnInteger.parseInt(userInput);
}catch(NumberFormatException e){
throw new InvalidInputException(e);
}
try{
return publicInterfaceMethod.invoke();
}catch(IllegalAccessException e){
throw new AssertionError(e);
}
Sometimes, those callers use Throwables.propagate. What are the disadvantages? The main one is that the meaning of the code is less obvious. What does throw Throwables.propagate(ioException) do? What does throw new RuntimeException(ioException) do? The two do the same thing, but the latter is more straightforward. The former raises questions: "What does this do? It isn't just wrapping inRuntimeException, is it? If it were, why would they write a method wrapper?" Part of the problem here, admittedly, is that "propagate" is a vague name. (Is it a way of throwing undeclared exceptions?) Perhaps "wrapIfChecked" would have worked better. Even if the method were called that, though, there would be no advantage to calling it on a known checked exception. There may even be additional downsides: Perhaps there's a more suitable type than a plain RuntimeException for you to throw -- say, IllegalArgumentException.
调用者使用Throwbles.propagate的缺点是什么?最主要的一个就是代码的含义会变得有点含糊不清. throw Throwables.propagate(ioException)是干嘛的? throw new RuntimeException(ipException)又是干嘛的? 这两种方法做了相同的事情, 但是后者表意会更清楚一些. 前者会有一些问题: "这个东西干嘛用? 他不就只是包了一层RuntimeException吗? 如果是这样, 用它干嘛?" 这是其中一部分问题, 不可否认的, "propagate" 是个表意不明的名字.也许"wrapIfChecked"会更好.就算方法叫"wrapIfChecked",用它来处理一个checked exception也没有什么好处.还有一些的缺点: 也许抛出一个 IllegalArgumentException 比一个简单的 RuntimeException 更合适.
We sometimes also see propagate used when the exception only might be a checked exception. The result is slightly smaller and slightly less straightforward than the alternative:
我们有时候看到propagate仅用来处理checked exception. 结果是比原来的代码更加表意不明
}catch(RuntimeException e){
throw e;
}catch(Exception e){
throw new RuntimeException(e);
}
}catch(Exception e){
throw Throwables.propagate(e);
}
However, the elephant in the room here is the general practice of converting checked exceptions to unchecked exceptions. It is unquestionably the right thing in some cases, but more frequently it's used to avoid dealing with a legitimate checked exception. This leads us to the debate over whether checked exceptions are bad idea in general. I don't wish to go into all that here. Suffice it to say that Throwables.propagate does not exist for the purpose of encouraging Java users to ignore IOException and the like.
然而, 转换checked exception 成 unchecked exception通常是人们忽视的问题. 毫无疑问在某些情况下这是正确的方法, 但是更多的情况是用来处理合法的checked exception. 这导致我们对转换checked exceptions是否是个坏主意进行了争论. 我在这不想讨论这些. 一句话来说 Throwables.propagate 的存在并不是用来鼓励Java程序员忽视IOException这样的异常的.
Controversial: Exception tunneling
争议2: 异常隧道(通信)
But what about when you're implementing a method that isn't allowed to throw exceptions? Sometimes you need to wrap your exception in an unchecked exception. This is fine, but again, propagate is unnecessary for simple wrapping. In fact, manual wrapping may be preferable: If you wrap every exception (instead of just checked exceptions), then you can unwrap every exception on the other end, making for fewer special cases. Additionally, you may wish to use a custom exception type for the wrapping.
如果你实现了一个不允许抛出异常的方法会怎样? 有时候你需要将你的异常包装成unchecked exception. 这很好, 但再次强调, propagate用于简单的包装是不必要的.实际上, 手动包装可能更好: 加入你需要包装所有异常(而不是仅仅包装checked exceptions), 那你可以在另一端对每一个异常进行解包装. 另外,你可能希望用一个自定义的异常来包装.
Controversial: Rethrowing exceptions from other threads
争议3: 从其他线程重抛出异常
try{
return future.get();
}catch(ExecutionException e){
throw Throwables.propagate(e.getCause());
}
There are multiple things to consider here:
这里有几件事情需要考虑
- The cause may be a checked exception. See "Converting checked exceptions to unchecked exceptions" above. But what if the task is known not to throw a checked exception? (Maybe it's the result of a Runnable.) As discussed above, you can catch the exception and throwAssertionError; propagate has little more to offer. For Future in particular, also consider Futures.get. (TODO: say something about its additional InterruptedException behavior)
- The cause may be a non-Exception, non-Error Throwable. (Well, it's unlikely to actually be one, but the compiler would force you to consider the possibility if you tried to rethrow it directly.) See "Converting from throws Throwable to throws Exception" above.
- The cause may be an unchecked exception or error. If so, it will be rethrown directly. Unfortunately, its stack trace will reflect the thread in which the exception was originally created, not the thread in which it is currently propagating. It's typically best to have include both threads' stack traces available in the exception chain, as in the ExecutionException thrown by get. (This problem isn't really about propagate; it's about any code that rethrows an exception in a different thread.)
- cause可能是一个checked exception. 查看上面说的"转换checked exception成unchecked exception". 但如果线程不是要抛出一个checked exception呢?(可能他是一个Runnable的result)就像上面讨论的那样, 你可以捕捉这个异常并 throw AssertionError; propagate还有一点可以提供.对于Future,同样也可以考虑Futures.get(TODO: 说说他另外的InterruptedException表现)
- cause可能是non-Exception, non-Error 的 Throwable (其实想说的是自定义Throwable吧)(好吧, 这看起来并不真的像个异常, 但如果你想抛出他编译器会强制你去处理他)查看上面的"将throws Throwable转换为throws Exception".
- cause可能是一个unchecked exception或者error.假如是这样, 他会被直接重抛出. 不幸的是,它的stack trace会被反射到创建这个exception的原始线程, 而不是抛出他的这个线程里.最好的解决这个问题方法是在异常链中包含两个线程stack,就像使用get来throw ExecutionException一样.(这其实不是关于propagate的问题,而是不同线程里重抛出异常的代码的问题)
Causal Chain
因果链
Guava makes it somewhat simpler to study the causal chain of an exception, providing three useful methods whose signatures are self-explanatory:
Guava使学习一个异常的因果链更简单, 他提供了3个签名是自解释的很有用的方法
Throwable getRootCause(Throwable) |
List<Throwable> getCausalChain(Throwable) |
String getStackTraceAsString(Throwable) |