解释Unchecked Exceptions — The Controversy表明这是为了提高可读性。
不捕获运行时异常难道不是一个巨大的代价吗?
编辑: 所谓的代价是指,在现场环境中而不是在编译期间获取运行时异常。如果此类异常在编译期间处理,就不可能发生错误泄漏。修复错误的成本随着检测到它们的阶段的延迟而增加。
未经检查的异常类(§11.1.1)免于编译时检查。
在未经检查的异常类中,错误类被豁免,因为它们可能发生在程序的许多点,并且从中恢复是困难或不可能的。声明这些异常的程序将变得混乱,毫无意义。尽管如此,复杂的程序可能仍希望捕获并尝试从其中一些条件中恢复。
在未经检查的异常类中,运行时异常类被豁免,因为在Java编程语言的设计者看来,声明这些异常并不能有效地帮助确立程序的正确性。Java编程语言的许多操作和构造都可能在运行时导致异常。对于Java编译器可用的信息以及编译器执行的分析级别,通常不足以确定这样的运行时异常不会发生,即使这对程序员来说是显而易见的。要求声明这样的异常类只会给程序员带来麻烦。
例如,某些代码可能实现一个循环数据结构,该结构通过构造永远不涉及空引用;程序员可以确定NullPointerException不会发生,但Java编译器很难证明。建立数据结构的这些全局属性所需的定理证明技术超出了本规范的范围。
作为开发人员,我们可以在一定程度上控制代码是否会抛出未捕获的异常。例如,我们可以在尝试执行会导致异常的操作之前检查是否为null
,或者编写代码以使该变量在第一次使用时就不可能为null
,从而避免遇到NullPointerException
。同样,我们可以通过实现合理性检查来确保输入绝对是数字,然后再将其解析为数字,以避免NumberFormatException
等错误。
如果您编写的代码合理,应该很少(如果有的话)遇到RuntimeException
(或其子类),因此要求您在每个可能引发异常的代码周围放置try-catch
块将导致即使是简单的类也会有成百上千个小型try-catch
块的可怕混乱,或者是一个捕获所有异常的巨大块,这两种情况都不是特别理想,并且会向您的代码添加大量不必要的膨胀。
被迫捕获所有未捕获的异常将使甚至像System.out.println()
这样的“简单”操作变得更加冗长。
如果我们被迫捕获所有未经检查的异常(注意:这是最坏情况,异常只会传播而不在API内部处理),那么我们需要编写什么来将空行打印到控制台:
(译注:此处指代码)System.out.println();
// ^ Theoretically this could throw a NullPointerException
try {
System.out.println();
} catch (NullPointerException e) {
// In practice this won't happen, but we're forced to deal with
// it all the same...
}
out
是什么,以及 println()
如何工作,以确定是否还有其他需要处理的内容。out
实际上是一个 PrintStream
对象,那么我们可以从它的 println()
方法中得出什么信息呢?println()
:public void println() {
newLine();
}
newLine()
的作用...private void newLine() {
try {
synchronized (this) {
ensureOpen();
textOut.newLine();
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush)
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
这里有更多关于NullPointerException
的来源,但我们已经捕获了它。 interrupt()
可能会(最终)抛出SecurityException
,还可能导致InterruptedException
,因此我们还需要处理这两个异常。
try {
System.out.println();
} catch (NullPointerException e) {
} catch (SecurityException e) {
} catch (InterruptedException e) {
}
textOut.newLine()
最终会进入 Writer#write(String, int, int)
,该方法处理一个 char[]
,因此我们立即有了一个 ArrayIndexOutOfBoundsException
的来源。它还调用 String#getChars(int, int, char[], int)
,这个方法本身也可能抛出 StringIndexOutOfBoundsException
。我们需要处理这个异常...
try {
System.out.println();
} catch (NullPointerException e) {
} catch (SecurityException e) {
} catch (InterruptedException e) {
} catch (ArrayIndexOutOfBoundsException e) {
} catch (StringIndexOutOfBoundsException e) {
}
它还调用了BufferedWriter#write(char[], int, int)
,这可能会抛出IndexOutOfBoundsException
异常...
try {
System.out.println();
} catch (NullPointerException e) {
} catch (SecurityException e) {
} catch (InterruptedException e) {
} catch (ArrayIndexOutOfBoundsException e) {
} catch (StringIndexOutOfBoundsException e) {
} catch (IndexOutOfBoundsException e) {
}
Error
子类。
如果我们真的被迫捕获所有异常,Java将因此变得更糟。
设计涉及权衡取舍。设计一个能够处理所有情况的系统并不可行;我们已经足够困难地设计出能够处理相当常见情况的系统。
在我看来,检查异常和未检查异常之间的权衡是一种语言设计问题,在这种权衡被规范化,API 设计人员(无论是系统还是应用程序)可以确定是否强制编译器捕获给定异常。
catch (Exception e) {}
,以便人们能指出他们的错误?在我看来,Java 提供了一种很好的灵活性 -- API 设计人员拥有选择的权利。
运行时异常是由以下原因引起的:
这两者都可以在代码本身中得到纠正,而且不应该发生。与预期会发生的已检查异常不同。
在下面的方法中,异常是由运行时错误的输入引起的。在调用函数之前应该纠正这个问题(错误的数据)。
Character.toChars(-2);
public static char[] toChars(int codePoint) {
if (isBmpCodePoint(codePoint)) {
return new char[] { (char) codePoint };
} else if (isValidCodePoint(codePoint)) {
char[] result = new char[2];
toSurrogates(codePoint, result, 0);
return result;
} else {
throw new IllegalArgumentException();
}
}
因为未经检查的异常不需要被捕获,而经过检查的异常需要被处理。因此,编译器“强制”你捕获经过检查的异常,并让你的未经检查的异常保持未被捕获状态。
更多解释请参见:Java中的已检查异常与未检查异常。