首页 文章

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

提问于
浏览
33

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

public abstract int read() throws IOException;

不声明 throws 语句 can't 抛出已检查异常的方法 .

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

第一个片段是一个问题的动机 .

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


回到 main question - 有没有理由以这种方式实现捕获检查的异常?这只是设计中的缺陷还是我遗漏了一些重要因素 - 可能是后向不兼容?如果在这种情况下只允许 RuntimeException 被捕获,可能会出现什么问题?非常感谢例子 .

3 回答

  • 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)
    抛出FirstException,SecondException {
    尝试{
    // ...
    }
    catch(例外e){
    扔掉;
    }
    }

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

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

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

  • 7

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

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

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

    尽管变量的类型是 Objecto 仍然存储 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:您没有捕获已检查的异常,您正在捕获任何异常 . 如果将异常强制转换为已检查的异常类型,则该异常仅成为已检查的异常 .

  • 19

    引用Java Language Specification, §11.2.3

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

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

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

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

    由OP编辑:答案的主要部分是问题示例仅适用于Exception类 . 通常,在代码的随机位置不允许捕获已检查的异常 . 对不起,如果我混淆了使用这些例子的人 .

相关问题