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

Ada*_*ker 34 java exception try-catch checked-exceptions unchecked-exception

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

public abstract int read() throws IOException;
Run Code Online (Sandbox Code Playgroud)

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

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

但是在安全方法中捕获已检查的异常在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?
    }
}
Run Code Online (Sandbox Code Playgroud)

实际上,没有.这有点好笑:编译器知道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
Run Code Online (Sandbox Code Playgroud)

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

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


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

Aas*_*set 19

引用Java语言规范,§11.2.3:

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

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

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

try {
    System.out.println("hello");
} catch (IOException e) {  // compilation error
    e.printStackTrace();
}
Run Code Online (Sandbox Code Playgroud)


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

  • 确切地说,这个规则早在try-multi-catch之前就存在了,可能是从1.0开始(但肯定是从1.2开始). (4认同)

rge*_*man 11

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

但是,在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;
   }
 }
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • @AdamSkywalker一直有可能捕获可以抛出的所有可能异常的超类,例如,想象一个文件操作方法,它包装了可以抛出`FileNotFoundException`和`IIOException`的代码.在`try-multi-catch`之前,处理两者的唯一方法是要么有两个相同的`catch`子句,要么捕获它的超类,例如`IOException`.或者"例外".两种解决方案在某种程度上都很糟糕,但第二种解决方案稍差. (4认同)

Jus*_*ica 7

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

捕获未经检查的异常Exception是有效的,因为未经检查的异常(又称RuntimeExceptions)是Exception的子类,它遵循标准的多态规则; 它不把捕捉到的异常到Exception,就像存储String在一个Object不转String 一个Object.多态性意味着一个可以容纳一个变量的变量可以包含ObjectObject(例如a String)派生的任何东西.同样,与Exception所有异常类型的超类一样,类型的变量Exception可以包含从中派生的任何类Exception,而无需将对象转换Exception.考虑一下:

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

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

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

这意味着实际上 "捕获任何与类兼容的东西Exception(即Exception从它衍生的任何东西)." 类似的逻辑也用于其他语言; 例如,在C++中,捉std::exception也将赶上std::runtime_error,std::logic_error,std::bad_alloc,任何正确定义的用户创建的例外情况,等等,因为它们都是从派生std::exception.

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

  • @AdamSkywalker请不要轻视那些只想帮助你的人.多态论是完全有效的,因为它的核心是`catch(Exception ex)`非常类似于`Exception ex = ...`. (4认同)