为什么捕获检查的异常允许不抛出异常的代码?

时间:2016-02-03 17:46:50

标签: java exception try-catch checked-exceptions unchecked-exception

在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被捕获,可能会出现什么问题?非常感谢例子。

3 个答案:

答案 0 :(得分:19)

引用Java Language Specification, §11.2.3

  

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

我猜这个规则早在Java 7之前就已经存在了,而Java 7并不存在多重捕获。因此,如果你有一个可以抛出大量异常的try块,捕获所有内容的最简单方法是捕获一个共同的超类(在最坏的情况下,Exception或{{1}如果你想抓住Throwable的话。

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

RuntimeException

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

答案 1 :(得分:11)

Java 7引入了more inclusive exception type checking

  

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

这篇文章讨论的是try块,它专门抛出FirstExceptionSecondException;即使catch块抛出Exception,该方法也只需要声明它抛出FirstExceptionSecondException,而不是Exception

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

这意味着编译器可以检测到test中抛出的唯一可能的异常类型是Error s或RuntimeException s,这两种类型都不需要被捕获。当你throw e;时,即使静态类型为Exception,也可以告诉它不需要声明或重新捕获。

但当你投射到Exception时,这会绕过那个逻辑。现在,编译器将其视为需要捕获或声明的普通Exception

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

答案 2 :(得分:7)

此处的问题是,选中/取消选中的异常限制会影响您的代码被抛出的内容,而不会影响 catch 的内容。虽然你仍然可以捕获任何类型的Exception,但是你唯一允许再次实际投掷的是未经检查的那些。 (这就是为什么将未经检查的异常转换为已检查的异常会破坏您的代码的原因。)

使用Exception捕获未经检查的异常是有效的,因为未经检查的异常(a.k.a。RuntimeException s)是Exception的子类,它遵循标准多态规则;它不会将捕获的异常变为Exception,就像在String中存储Object一样不会将String 转变为 Object。多态性意味着可以容纳Object的变量可以包含从Object派生的任何内容(例如String)。同样,由于Exception是所有异常类型的超类,因此Exception类型的变量可以包含从Exception派生的任何类,而无需将对象转换为 Exception。考虑一下:

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

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

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

这意味着实际上“捕获与类Exception兼容的任何内容(即Exception及其衍生的任何内容)。”类似的逻辑也用于其他语言;例如,在C ++中,捕获std::exception也会捕获std::runtime_errorstd::logic_errorstd::bad_alloc,任何正确定义的用户创建的异常,依此类推,因为它们都是派生自std::exception

tl; dr:你没有捕获已检查的例外,你正在捕捉任何例外。如果将异常强制转换为已检查的异常类型,则该异常仅成为已检查的异常。