17

为什么在 Java 中Exception即使它没有被抛出,我们也能捕捉到它,但我们不能捕捉到它的子类(“unchecked” RuntimeExceptions 和它的子类除外)。示例代码:

class Test {
    public static void main(String[] args) {
        try {
            // do nothing
        } catch (Exception e) {
            // OK           
        }

        try {
            // do nothing
        } catch (IOException e) {
               // COMPILER ERROR: Unreachable catch block for IOException.
               //This exception is never thrown from the try statement body
        }       
    }
}

有任何想法吗?

4

7 回答 7

28

ARuntimeException可以被任何代码抛出。换句话说,编译器不能轻易预测什么样的代码会抛出它。ARuntimeException可以被catch(Exception e)块捕获。

IOException但是,这是一个检查异常——只有声明为抛出它的方法调用才能这样做。编译器可以(合理地)确信它不可能发生,除非有声明为抛出它的方法调用。

Java 编译器根本不考虑“try 块中根本没有代码”的情况——它总是允许您捕获未经检查的异常,因为在所有合理的情况下,都会有可能引发未经检查的异常的代码。

从JLS 的第 14.21 节:

如果满足以下两个条件,则可以访问 catch 块 C:

  • try 块中的某些表达式或 throw 语句是可访问的,并且可以抛出一个异常,其类型可分配给 catch 子句 C 的参数。(如果包含它的最里面的语句是可访问的,则表达式被认为是可访问的。)
  • try 语句中没有更早的catch 块A,使得C 的参数类型与A 的参数类型相同或子类。

可以说,编译器应该意识到在你的第一种情况下,try 块中没有表达式......对我来说,这看起来仍然是一个无法访问的 catch 子句。

编辑:如评论中所述,第 14.20 节包含以下内容:

如果catch子句捕获已检查的异常类型E1但不存在已检查的异常类型E2使得以下所有条件都成立,则这是一个编译时错误:

  • E2 <: E1
  • try子句对应的blockcatch可以抛出E2
  • catch紧随其后的 try 语句的前面块没有捕获E2或E2的超类型。

除非E1是 Exception 类。

所以看起来这就是你实际遇到的问题,但规范并不像 14.21 中无法到达的 catch 块那样清晰。

于 2011-05-05T15:01:11.130 回答
5

只有当编译器预测代码中可能存在引发 IOException 的内容时,才能捕获 IO 异常。因此,您会收到一条警告,即永远不会从 try 语句主体中抛出 IO 异常(因为 try 的主体中没有任何内容)。

于 2014-02-12T07:25:09.613 回答
3

因为对于已检查的异常,抛出它们的方法必须通过“throws”关键字显式声明这一事实,因此,如果在您的情况下一个块没有“throws IOException”,则编译器有信息不可能出现 IOException被抛出,所以你抓到后做什么,都无法触及。

于 2011-05-05T15:01:34.077 回答
3

您无法捕获未抛出的已检查异常,因为它们无法被抛出。您可以捕获Exception,因为未经检查的运行时异常是Exception并且可能会被抛出。

于 2011-05-05T15:01:37.650 回答
1

IOException是一个检查异常,仅由 IO 相关代码抛出。由于你的 try 块什么都不做,任何与 IO 相关的事情都不会发生,IOExceptions 永远不会被抛出,所以 catch 块永远不会被执行,编译器也不会让你绕过它。正如您所说,异常可能是指随时可能发生的未经检查的运行时异常。这是未检查异常和检查异常之间的主要区别,这就是编译器不强制代码捕获每个可能的运行时异常的原因。

于 2011-05-05T15:04:00.670 回答
0

简单的 Java 假定任何代码行都可以抛出泛型Exceptionor Throwable,即。OutOfMemoryException这是一个Error相当的Exception。同样适用于 NPE。

IOException是只能由托管代码引发的特定异常,因此如果您的 catch 块中没有 I/O 调用,您的编译器就没有机会捕获它。

只是为了与 C# 世界进行比较,在 C# 中这样的代码会被编译,但这是一个概念上的错误,因为如果你不做任何事情,你就不会到达 catch 块。ReSharper 之类的工具可以就此发出警告。

于 2011-05-05T15:01:03.237 回答
0

如果我们从上面每个人的评论中总结,可以得出结论,像 IOException 及其子类这样的完全检查的异常意味着要由编译器严格检查,并由相关的 catch 子句预期抛出。但是在 RuntimeException 和 Exception 或 Throwable 的情况下(它们都是部分检查的异常,因为它们有 RuntimeException 作为子/孙)编译器根本不能在编译时说并允许它通过。

所以..

try{
  // Empty - valid
} catch(Exception or Throwable or any Runtime Exception ){

}

try{
  // Empty - invalid and compile time error
} catch (Any fully checked Exception like IOException, FileNotFoundException, EOF/Interruped etc){

}
于 2021-01-06T19:30:47.210 回答