Using checked exceptions exclusively leads to several problems:
使用checked异常会导致一些问题:
-
Too much code
-
太多的代码
Developers will become frustrated by having to catch checked exceptions that they can't reasonably handle (of the "something when horribly wrong" variety) and write code that ignores (swallows) them. Agreed: this is indefensible coding practice, but experience shows that it happens more often than we like to think. Even good programmers may occasionally forget to "nest" exceptions properly (more about this below), meaning that the full stack trace is lost, and the information contained in the exception is of reduced value. -
开发人员会觉得很失望,不得不抓住checked异常他们不能合理的处理的(当一些可怕的错误),和写代码忽略他们。赞同:这是个无根据的代码实践,但是经验显示他的发生超出了我们的想象。好的程序员可能偶尔的忘记了“捕获”异常属性,意味着所有的栈记录丢失了,信息包含在异常中的是简化了的。
-
Unreadable code
-
不可达代码
Catching exceptions that can't be appropriately handled and rethrowing them (wrapped in a different exception type) performs little useful function, yet can make it hard to find the code that actually does something. The orthodox view is that this bothers only lazy programmers, and that we should simply ignore this problem. However, this ignores reality. For example, this issue was clearly considered by the designers of the core Java libraries. Imagine the nightmare of having to work with collections interfaces such as java.util.Iterator if they threw checked, rather than unchecked, exceptions. The JDO API is another example of a Sun API that uses unchecked exceptions. By contrast, JDBC, which uses checked exceptions, is cumbersome to work with directly. -
捕获异常不能被合适的处理和重新抛出(包装在一个不同的异常类型中)执行少量有用的功能,还可能使它很难去查找实际执行的代码。
-
Endless wrapping of exceptions
-
无尽的包装异常
A checked exception must be either caught or declared in the throws clause of a method that encounters it. This leaves a choice between rethrowing a growing number of exceptions, or catching low-level exceptions and rethrowing them wrapped in a new, higher-level exception. This is desirable if we add useful information by doing so. However, if the lower-level exception is unrecoverable, wrapping it achieves nothing. Instead of an automatic unwinding of the call stack, as would have occurred with an unchecked exception, we will have an equivalent, manual, unwinding of the call stack, with several lines of additional, pointless, code in each class along the way. It was principally this issue that prompted me to rethink my attitude to exception handling. -
一个checked异常必须被捕获或者声明在方法的throws子句中。这留下了一个选择在重新抛出一系列的异常或者捕获低级别的异常然后重新抛出它们包装成一个新的,更高级别的异常。如果我么增加有用的信息这是非常好的。然而,如果低级别的异常是不可恢复的,包装它实现不了什么。取而代之的是一个自动解除堆栈调用,作为会发生和一个unchecked异常,我们将有一个等效的,手动的解除堆栈调用,在每个类中用几行额外的,无意义的代码。主要是这个问题促使我重新改正我对异常处理的态度。
-
Fragile method signatures
-
脆弱的方法签名
Once many callers use a method, adding an additional checked exception to the interface will require many code changes. -
一旦很多调用者继续一个方法,对接口增加一个额外的checked异常将要求许多代码改变。
-
Checked exceptions don't always work well with interfaces
-
checked异常并不总是适合接口
Take the example of the file system being full in the Java Tutorial. This sounds OK if we're talking about a class that we know works with the file system. What if we're dealing with an interface that merely promises to store data somewhere (maybe in a database)? We don't want to hardcode dependence on the Java I/O API into an interface that may have different implementations. Hence if we want to use checked exceptions, we must create a new, storage-agnostic, exception type for the interface and wrap file system exceptions in it. Whether this is appropriate again depends on whether the exception is recoverable. If it isn't, we've created unnecessary work. -
I suggest the following guidelines for choosing between checked and unchecked exceptions:
Question
Example
Recommendation if the answer is yes
Should all callers handle this problem? Is the exception essentially a second return value for the method?
-
所有的调用者必须处理这个问题么?异常必须有第2个返回值对于这个方法?
Spending limit exceeded in a processInvoice() method
Define and used a checked exception and take advantage of Java's compile-time support.
Will only a minority of callers want to handle this problem?
-
仅仅少数调用者需要去处理这个问题?
JDO exceptions
Extend RuntimeException. This leaves callers the choice of catching the exception, but doesn't force all callers to catch it.
Did something go horribly wrong? Is the problem unrecoverable?
-
这会使事情变得很糟糕么?这个问题是不可达的么?
A business method fails because it can't connect to the application database
Extend RuntimeException. We know that callers can't do anything useful besides inform the user of the error.
Still not clear?
Extend RuntimeException. Document the exceptions that may be thrown and let callers decide which, if any, they wish to catch.
One reason sometimes advanced for avoiding runtime exceptions is that an uncaught runtime exception will kill the current thread of execution. This is a valid argument in some situations, but it isn't normally a problem in J2EE applications, as we seldom control threads, but leave this up to the application server. The application server will catch and handle runtime exceptions not caught in application code, rather than let them bubble up to the JVM. An uncaught runtime exception within the EJB container will cause the container to discard the current EJB instance. However, if the error is fatal, this usually makes sense.
一个重要的原因避免运行时异常是由于未捕获的运行时异常将杀掉当前线程的执行。在某些情形下这是一个有效的论点,但这通常不是一个问题在j2ee应用中,因为我们很少控制线程,留给这个到应用服务器的顶端。应用服务器将捕获和处理没有在代码中捕获的运行时异常,而不是让他们抵达到jvm。一个未捕获的运行时异常在ejb容器中将引起容器抛弃当前的ejb实例。然而,如果错误是致命的,这通常是有意义的。
Good Exception Handling Practices
好的异常实践
Whether we used checked or unchecked exceptions, we'll still need to address the issue of "nesting" exceptions. Typically this happens when we're forced to catch a checked exception we can't deal with, but want to rethrow it, respecting the interface of the current method. This means that we must wrap the original, "nested" exception within a new exception.
是否去用checked或unchecked异常,我们任然需要解决“嵌套”异常的问题。通常这发生在当我们被迫去捕获一个我们不能处理的checked异常,但是我们想重新抛出它,关于当前方法的接口而言。这意味这我们必须包装原来的,“嵌套的”异常在一个新的异常中。
Some standard library exceptions, such as javax.servlet.ServletException, offer such wrapping functionality. But for our own application exceptions, we'll need to define (or use existing) custom exception superclasses that take a "root cause" exception as a constructor argument, expose it to code that requires it, and override the printStackTrace() methods to show the full stack trace, including that of the root cause. Typically we need two such base exceptions, one for checked and on for unchecked exceptions.
一些标准库异常,例如javax.servlet.ServletException,提供了这样的包装功能。但是对于我么自己的应用异常,我们需要去定义(或者用存在的)定制化的异常父类,把一个“根源”异常作为构造函数参数,暴露它到需要的编码中,重写printStackTrace()方法我展现所有的堆栈踪迹,包含这个根源。通常我们需要这个这样的基类异常,一个为checked另一个为unckecked异常。
Note
注意
This is no longer necessary in Java 1.4, which supports exception nesting for all exceptions. We'll discuss this important enhancement below.
在java1.4中这是不必要的,它支持异常嵌套在所有的异常中。我们将在下面讨论这个重要的增强。
In the generic infrastructure code accompanying our sample application, the respective classes are com.interface21.core.NestedCheckedException and com.interface21.core.NestedRuntimeException. Apart from being derived from java.lang.Exception and java.lang.RuntimeException respectively, these classes are almost identical. Both these exceptions are abstract classes; only subtypes have meaning to an application. The following is a complete listing of NestedRuntimeException:
在通用的基础设施代码中伴随着我们的简易应用,这2个类是com.interface21.core.NestedCheckedException and com.interface21.core.NestedRuntimeException.各自从java.lang.Exception and java.lang.RuntimeException中派生出来,这些类几乎是相同的。这2个类都是抽象类;仅仅子类型对应用是有意义的。下面是一个完全的NestedRuntimeException列表:
package com.interface21.core;
import java.io.PrintStream;
import java.io.PrintWriter;
public abstract class NestedRuntimeException extends RuntimeException {
private Throwable rootCause;
public NestedRuntimeException (String s) {
super(s);
}
public NestedRuntimeException(String s, Throwable ex) {
super (s);
rootCause = ex;
}
public Throwable getRootCause() {
return rootCause;
} public String getMessage() {
if (rootCause == null) {
return super.getMessage();
} else {
return super.getMessage() + "; nested exception is: \n\t" +
rootCause.toString();
}
} public void printStackTrace (PrintStream ps) {
if (rootCause == null) {
super.printStackTrace(ps);
} else {
ps.println(this);
rootCause.printStackTrace(ps);
}
}
public void printStackTrace(PrintWriter pw) {
if (rootCause == null) {
super.printStackTrace(pw);
} else {
pw.println(this);
rootCause.printStackTrace(pw);
}
}
public void printStackTrace() {
printStackTrace(System.err);
}
}
Java 1.4 introduces welcome improvements in the area of exception handling. There is no longer any need for writing chainable exceptions, although existing infrastructure classes like those shown above will continue to work without a problem. New constructors are added to java.lang.Throwable and java.lang.Exception to support chaining, and a new method void initCause (Throwable t) is added to java.lang.Throwable to allow a root cause to be specified even after exception construction. This method may be invoked only once, and only if no nested exception is provided in the constructor.
java1.4引入了受欢迎改动在异常处理的地方。再也不需要写可链接的异常,即使存在基础类像上面显示的将继续毫无问题的工作。新的构造方法被添加到
java.lang.Throwable and java.lang.Exception 以支持链式处理,一个新方法void initCause (Throwable t)被增加到java.lang.Throwable 允许指定根本原因即使在异常构造后。这个方法可能被调用仅仅一次,仅仅如果嵌套异常在构造方法中被提供。
Java 1.4-aware exceptions should implement a constructor taking a throwable nested exception and invoking the new Exception constructor. This means that we can always create and throw them in a single line of code as follows:
在java1.4中,意识到异常必须实现一个构造作为一个可抛出的嵌套异常,调用这个新的异常构造。这意味这我们能总是创建然后抛出他们在几行代码中,像下面的:
catch (RootCauseException ex) {
throw new MyJava14Exception("Detailed message", ex);
}
If an exception does not provide such a constructor (for example, because it was written for a pre Java 1.4 environment), we are guaranteed to be able to set a nested exception using a little more code, as follows:
如果一个异常没有提供这样的构造方法(例如,因为它是为java1.4以前的环境写的),我们确保可能设置一个嵌套异常用一点点更多的代码,像下面的:
catch (RootCauseException ex) {
MyJava13Exception mex = new MyJava13Exception("Detailed message");
mex.initCause(ex);
throw mex;
}
When using nested exception solutions such as NestedRuntimeException, discussed above, follow their own conventions, rather than Java 1.4 conventions, to ensure correct behavior.
当使用嵌套异常解决方案如NestedRuntimeException,上面所讨论的,遵循自己的惯例,而不是Java 1.4规范,以确保正确的行为。