为什么允许对不引发异常的代码捕获已检查的异常



在Java中,抛出检查异常(异常或其子类型-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,可能会出现什么问题?非常感谢示例。

引用Java语言规范§11.2.3:

如果 catch 子句可以捕获选中的异常类 E1,则编译时错误,并且对应于

catch 子句的 try 块不能抛出作为 E1 的子类或超类的已检查异常类的情况,除非 E1 是异常或异常的超类。

我猜这条规则早在Java 7之前就已经出现,当时不存在多重捕获。因此,如果你有一个可以抛出大量异常的try块,那么捕获所有内容的最简单方法是捕获一个公共超类(在最坏的情况下,Exception ,或者Throwable如果你想捕获Error s(。

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

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


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

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

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

这段话讲的是一个专门抛出FirstExceptionSecondExceptiontry块;即使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

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

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

使用 Exception 捕获未选中的异常是有效的,因为未选中的异常(也称为 RuntimeException s( 是异常的子类,它遵循标准的多态性规则;它不会将捕获的异常变成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

dr:你没有捕获已检查的异常,而是捕获了任何异常。 仅当将异常转换为已检查异常类型时,该异常才会成为已检查的异常。

相关内容

  • 没有找到相关文章

最新更新