35

在 Java 中,抛出已检查异常(Exception或其子类型 - IOException、InterruptedException 等)的方法必须声明throws语句:

public abstract int read() throws IOException;

不声明throws语句的方法不能抛出已检查异常。

public int read() { // does not compile
    throw new IOException();
}
// Error: unreported exception java.io.IOException; must be caught or declared to be thrown

但是在安全方法中捕获已检查的异常在 java 中仍然是合法的:

public void safeMethod() { System.out.println("I'm safe"); }

public void test() { // method guarantees not to throw checked exceptions
    try {
        safeMethod();
    } catch (Exception e) { // catching checked exception java.lang.Exception
        throw e; // so I can throw... a checked Exception?
    }
}

实际上,没有。这有点好笑:编译器知道e不是检查异常并允许重新抛出它。事情甚至有点荒谬,这段代码无法编译:

public void test() { // guarantees not to throw checked exceptions
    try {
        safeMethod();
    } catch (Exception e) {        
        throw (Exception) e; // seriously?
    }
}
// Error: unreported exception java.lang.Exception; must be caught or declared to be thrown

第一个片段是提出问题的动机。

编译器知道不能在安全方法中抛出已检查的异常 - 所以也许它应该只允许捕获未检查的异常?


回到主要问题- 是否有任何理由以这种方式实现捕获检查的异常?这只是设计中的一个缺陷,还是我错过了一些重要因素——可能是向后不兼容?RuntimeException如果只允许在这种情况下被捕获,可能会出现什么问题?非常感谢示例。

4

3 回答 3

19

引用Java 语言规范,§11.2.3

如果 catch 子句可以捕获已检查的异常类 E1,并且与该 catch 子句对应的 try 块不能抛出作为 E1 的子类或超类的已检查异常类,则这是编译时错误,除非 E1 是异常或异常的超类。

我猜这个规则早在 Java 7 之前就已经出现了,在 Java 7 中不存在多重捕获。因此,如果您有一个try可能引发大量异常的块,那么捕获所有异常的最简单方法是捕获一个公共超类(在最坏的情况下Exception,或者Throwable如果您也想捕获Errors)。

请注意,您可能无法捕获与实际抛出的内容完全无关的异常类型 - 在您的示例中,捕获Throwable不是 a 的任何子类RuntimeException将是一个错误:

try {
    System.out.println("hello");
} catch (IOException e) {  // compilation error
    e.printStackTrace();
}


由 OP 编辑​​:答案的主要部分是问题示例仅适用于 Exception 类的事实。通常不允许在代码的随机位置捕获已检查异常。抱歉,如果我使用这些示例使某人感到困惑。

于 2016-02-03T18:01:41.377 回答
11

Java 7 引入了更具包容性的异常类型检查

但是,在 Java SE 7 中,您可以在 rethrowException 方法声明的 throws 子句中指定异常类型 FirstException 和 SecondException。Java SE 7 编译器可以确定语句 throw e 抛出的异常一定来自 try 块,而 try 块抛出的异常只能是 FirstException 和 SecondException。

这段话是在讨论一个try专门抛出FirstException和的块SecondException;即使该catch块 throws Exception,该方法也只需要声明它会抛出FirstExceptionand SecondException,而不是Exception

public void rethrowException(String exceptionName)
 throws FirstException, SecondException {
   try {
     // ...
   }
   catch (Exception e) {
     throw e;
   }
 }

这意味着编译器可以检测到抛出的唯一可能的异常类型testErrors 或RuntimeExceptions,它们都不需要被捕获。当 youthrow e;时,即使静态类型为Exception,它也可以告诉它不需要声明或重新捕获。

但是,当您其转换为时Exception,这会绕过该逻辑。现在编译器把它当作一个普通的Exception,需要被捕获或声明。

将此逻辑添加到编译器的主要原因是允许程序员在重新抛出捕获那些特定子类型throws的通用时仅指定子句中的特定子类型。Exception但是,在这种情况下,它允许您捕获一般情况,Exception而不必在子句中声明任何异常throws,因为没有可以抛出的特定类型是检查异常。

于 2016-02-03T17:52:49.827 回答
7

这里的问题是已检查/未检查的异常限制会影响您的代码允许抛出的内容,而不是允许捕获的内容。虽然您仍然可以捕获任何类型的Exception,但您唯一可以实际再次抛出的是未经检查的。(这就是为什么将未检查的异常转换为已检查的异常会破坏您的代码。)

捕获未经检查的异常Exception是有效的,因为未经检查的异常(aka RuntimeExceptions)是 Exception 的子类,并且它遵循标准的多态规则;它不会将捕获的异常转换为Exception,就像将 a 存储String在 anObject不会将 theString 转换为一样Object。多态性意味着可以容纳 a 的变量可以容纳从(例如 a )Object派生的任何东西。同样,与所有异常类型的超类一样,类型变量可以保存从 派生的任何类,而无需将对象转换为。考虑一下:ObjectStringExceptionExceptionExceptionException

import java.lang.*;
// ...
public String iReturnAString() { return "Consider this!"; }
// ...
Object o = iReturnAString();

尽管变量的类型是Objecto但仍然存储 a String,不是吗?同样,在您的代码中:

try {
    safeMethod();
} catch (Exception e) { // catching checked exception
    throw e; // so I can throw... a checked Exception?
}

这意味着实际上是“捕获任何与类兼容的东西Exception(即Exception和从它派生的任何东西)”。类似的逻辑也用于其他语言;例如,在 C++ 中,捕获 astd::exception还将捕获std::runtime_errorstd::logic_errorstd::bad_alloc、任何正确定义的用户创建的异常等等,因为它们都派生自std::exception.

tl; dr:您没有捕获已检查的异常,而是捕获了任何异常。只有将异常转换为受检异常类型时,该异常才会成为受检异常。

于 2016-02-03T18:23:57.037 回答