Java的try catch块中可捕获的Exception的规则是什么?

时间:2019-02-17 02:09:55

标签: java exception-handling

我正在阅读Java语言规范,this部分引起了我的注意:

  

如果catch子句可以捕获checked,则是编译时错误   异常类E1,而try块不是这种情况   对应于catch子句可以抛出一个检查异常类   是E1的子类或超类,除非E1是Exception或   Exception的超类。

我不理解的部分在上面以粗体强调。

给出以下层次结构

class A extends Exception {}
class B extends A {}
class C extends B {}

这是我的理解:

  

与catch子句相对应的try块可以抛出一个经过检查的异常类,该类是E1的子类

void f() {
    try {
        throw new C();
    } catch (B b) {

    }
}

可以编译。.

但是什么意思

  

与catch子句相对应的try块可以抛出一个checked   E1的超类的异常类

这是我所了解的,不会编译(我没想到会这样,但是JLS使我如愿以偿)

void f() {
    try {
        throw new A();
    } catch (B b) {  // will not compile

    }
}

由于规范错误的可能性很小,您能否帮助我理解我所困惑的部分的含义,最好是通过一个示例进行说明?

3 个答案:

答案 0 :(得分:2)

这确实很棘手。要理解这一点,您应该回到面向对象范例中的继承概念:

  • 如果我想创建一个ArrayList,可以用两种不同的方法来做到这一点:
    1. 第一个是忽略所有继承概念,并对ArrayList进行硬编码,并将其类型设置为ArrayList本身:
      ArrayList<String> list = new ArrayList<String>();
      
    2. 第二个是继承的概念,我们尝试在更高级别的类/接口下对其进行实例化:
      List<String> list = new ArrayList<String>();
      

第二种方法更好,由于许多原因,我将不予提及,因为它不在答案范围内,但是由于ArrayList继承自List,因此我们可以将新的ArrayList实例化为List对象,但这是不可能的之所以这样,是因为List不会扩展或继承自ArrayList

ArrayList<String> list = new List<String>();

相同的想法适用于Java的可捕获异常。按照您的示例,如果我有

class A extends Exception {}
class B extends A {}
class C extends B {}

当我尝试捕获异常时,我想先尝试使用较低级别的类(C)(即更具体),因为我对它的含义有更大的把握:

try {
    // something here that throws C
} catch (C exc) {
    // Great! Its C! I know exactly what should I do!
} catch (B exc) {
    // Not that great, it might not be that hard to figure out what caused this
} catch (A exc) {
    // I still have some idea of what might have thrown this exception
} catch (Exception e) {
    // Oh boy, its not an exception that I mapped before... It might be harder than I thought... :(
}

但是我们必须遵循ArrayList的相同思路,无法在例外情况下收到List对象:

  • catch (C exc)catch (B exc)catch (A exc)catch (Exception e)会捕获C异常
  • catch (B exc)catch (A exc)catch (Exception e)可以捕获B异常
  • catch (A exc)catch (Exception e)可以捕获异常
  • 依此类推...

由于A比B高,所以catch (B exc)无法捕获它。这就是为什么它不能在您的示例中编译的原因:

void f() {
    try {
        throw new A();
    } catch (B b) {  // will not compile

    }
}

page you linked的规范中,在11.3之前有一个代码段,可以例证您的要求,因为正如我已经解释的那样,您理解得很对:

  

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

这意味着您无法捕获上一个catch块的子类,并且Java编译器会将其视为错误。例如,如果您尝试这样做

try {
    throw new C();
} catch (A exc) {
    // do something here, because the exception will be caught
} catch (B exc) {
    // since the exception has been caught before and B is subclass of A,
    // this causes the compilation error that JLS was talking about:
    // you cannot catch a subclass of a class previously caught.
}

答案 1 :(得分:2)

我认为理解这一点的简单方法是举一个具体的例子:

public void doOpen(File file) throws IOException {
    new FileInputStream(file);
}

请注意,该方法被声明为抛出IOException而不是其子类FileNotFoundException

现在让我们尝试捕获一个异常,该异常我们知道文件不存在时会抛出该异常:

try {
    doOpen(someFile);
} catch (FileNotFoundException ex) {
    // print a message
} 

JLS说:

  

如果catch子句可以捕获经过检查的异常类E1,则是编译时错误,并且与catch子句相对应的try块不能抛出作为子类的经过检查的异常类不是这种情况<或E1 的超类,除非E1ExceptionException的超类。

在示例代码的catch子句中,JLS中的E1FileNotFoundException。该方法声明为抛出IOException。如果突出显示的子句不存在(即“或E1的超类”),则该捕获将不合法。但这显然应该是合法的,因为doOpen方法显然可以抛出FileNotFoundException

答案 2 :(得分:0)

请注意,已检查的异常表示未扩展Error或RuntimeException的任何异常类。简而言之,如果您在catch中声明除Exception或throwable以外的已检查异常,则编译器将抱怨try块永远不会抛出Exception。

我相信E1的超类可能是指E1的实例或E1的子类可以存储为E1的超类的情况。在这种情况下,编译器可能会在声明的逻辑之外进行进一步的检查,以确保在抱怨之前,E1超类实例实际上不是E1或E1的子类。

如果我们谈论的是语言规范,则在进一步检查的情况下,编译器的实际输出可能会有所不同,前提是对于特定说明的情况,输出是相同的。一个编译器实际上可以检查E1实例是否可以作为超类抛出,而另一个编译器可以简单地显示编译错误,前提是每个实现均应遵循规范。