我正在阅读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
}
}
既然说明书不太可能出错,你能否帮我理解我所困惑的部分的含义,最好用一个例子来证明?
这确实非常棘手。要理解这一点,您应该回到面向对象范式中的继承概念:
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.
}
我认为理解这个的简单方法是用一个具体的例子:
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
。
请注意,已检查的异常表示未扩展Error或RuntimeException的任何异常类。它只是说如果你在catch中声明一个除Exception或throwable之外的已检查异常,编译器会抱怨如果try块永远不会抛出Exception。
我认为E1的超类可能是指E1的实例或E1的子类可以存储为E1的超类的情况。在这种情况下,编译器可以在所述逻辑之外运行进一步的检查,以确保E1超类实例在其投诉之前实际上不是E1或E1的子类。
如果我们讨论语言规范,编译器的实际输出可能会因进一步检查部分而有所不同,前提是输出对于具体说明的情况是相同的。一个编译器实际上可以检查E1实例是否可以作为超类抛出而另一个可能只是显示编译错误,前提是每个实现都遵守规范。