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

问题描述 投票:0回答:3

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

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

我不明白的部分在上面用粗体强调。

给出以下层次结构

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块可以抛出一个已检查的异常类,它是E1的超类

?

这就是我所理解的,它不会编译(我没想到它,但是JLS让我成为了它的事情)

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

    }
}

既然说明书不太可能出错,你能否帮我理解我所困惑的部分的含义,最好用一个例子来证明?

java exception-handling
3个回答
2
投票

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

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

第二种方法更好的原因有很多我不会提及,因为它不在答案的范围内,但由于ArrayList继承自List,我们可以将一个新的ArrayList实例化为List对象,但不可能这样做因为List不延伸或继承自ArrayList

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

同样的想法适用于Java的Catchable Exceptions。按照你的例子,如果我有:

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是异常或异常的超类。

这意味着您无法捕获前一个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.
}

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的子类或超类的已检查异常类,除非E1是,否则这是编译时错误。 ExceptionException的超类。

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


0
投票

请注意,已检查的异常表示未扩展Error或RuntimeException的任何异常类。它只是说如果你在catch中声明一个除Exception或throwable之外的已检查异常,编译器会抱怨如果try块永远不会抛出Exception。

我认为E1的超类可能是指E1的实例或E1的子类可以存储为E1的超类的情况。在这种情况下,编译器可以在所述逻辑之外运行进一步的检查,以确保E1超类实例在其投诉之前实际上不是E1或E1的子类。

如果我们讨论语言规范,编译器的实际输出可能会因进一步检查部分而有所不同,前提是输出对于具体说明的情况是相同的。一个编译器实际上可以检查E1实例是否可以作为超类抛出而另一个可能只是显示编译错误,前提是每个实现都遵守规范。

© www.soinside.com 2019 - 2024. All rights reserved.