我正在阅读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
}
}
由于规范错误的可能性很小,您能否帮助我理解我所困惑的部分的含义,最好是通过一个示例进行说明?
答案 0 :(得分:2)
这确实很棘手。要理解这一点,您应该回到面向对象范例中的继承概念:
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的可捕获异常。按照您的示例,如果我有
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
的超类,除非E1
是Exception
或Exception
的超类。
在示例代码的catch子句中,JLS中的E1
是FileNotFoundException
。该方法声明为抛出IOException
。如果突出显示的子句不存在(即“或E1
的超类”),则该捕获将不合法。但这显然应该是合法的,因为doOpen
方法显然可以抛出FileNotFoundException
。
答案 2 :(得分:0)
请注意,已检查的异常表示未扩展Error或RuntimeException的任何异常类。简而言之,如果您在catch中声明除Exception或throwable以外的已检查异常,则编译器将抱怨try块永远不会抛出Exception。
我相信E1的超类可能是指E1的实例或E1的子类可以存储为E1的超类的情况。在这种情况下,编译器可能会在声明的逻辑之外进行进一步的检查,以确保在抱怨之前,E1超类实例实际上不是E1或E1的子类。
如果我们谈论的是语言规范,则在进一步检查的情况下,编译器的实际输出可能会有所不同,前提是对于特定说明的情况,输出是相同的。一个编译器实际上可以检查E1实例是否可以作为超类抛出,而另一个编译器可以简单地显示编译错误,前提是每个实现均应遵循规范。