捕获异常时为什么顺序很重要?

24
我必须用一些代码来回答这个问题:
假设我写了以下方法规范:
public void manipulateData() throws java.sql.SQLException, java.sql.SQLDataException 您正在为将使用此方法的数据库程序编写代码,并且您希望单独处理每个异常。try/catch子句应该是什么样子?
您可以使用no-ops--空块{}--作为catch子句内容。
我们只对语法和语句的结构感兴趣。
try {

} catch(java.sql.SQLException e) {

}
catch(java.sql.SQLDataException e) {

}

他不接受这个答案的原因是:你的catch语句顺序不正确。你能解释一下为什么顺序很重要吗?他的回答正确吗?

4
异常只会被捕获一次。如果第一个异常与类型匹配,则第二个及之后的异常将被跳过,即使它们可能是子类型。因此,通常先捕获子类型,然后再是父类型,以便准确捕获不同类型的异常。 - shuangwhywhy
2
这会导致编译错误“无法访问的代码”! - Grijesh Chauhan
8个回答

42
是的,他是正确的。如您在Javadoc中所见,SQLDataExceptionSQLException的子类。因此,您的答案是错误的,因为它会在第二个catch中创建一块无法到达的代码块。
在Java中,此代码甚至不会编译。在其他语言中(例如Python),这样的代码会创建一个微妙的错误,因为SQLDataException实际上会在第一个块中捕获,而不是在第二个块中捕获(如果它不是子类的话)。
如果您回答catch(java.sql.SQLException | java.sql.SQLDataException e) { },那么仍然是不正确的,因为问题要求特别处理每个异常。
正确的答案在Grijesh's answer中。

实际上,如果我没有记错的话,在那个时候 SQLDataException 甚至不可用于捕获,这会导致一个错误。 - ratchet freak
4
@ratchetfreak 是的。如果在编译时出现“exception java.sql.SQLDataException has already been caught”错误,编译器会自动检测到这个问题。但即使编译器没有检测到,这仍然是一个逻辑错误,因为第二个catch将无法到达。 - user000001
1
这个问题同样适用于其他编程语言,在这种情况下,编译器可能无法产生错误(例如在Python中,无法在编译时进行这些检查),那么你将会遇到一个bug。 - Bakuriu

22
在Java中,你必须先放置最不具体的异常。接下来的异常必须更具体(当它们相关时)。例如:如果你首先放置了最具体的异常(Exception),那么接下来的异常将永远不会被调用。以下是示例代码:
try {
     System.out.println("Trying to prove a point");
     throw new java.sql.SqlDataException("Where will I show up?");
}catch(Exception e){
     System.out.println("First catch");
} catch(java.sql.SQLException e) {
     System.out.println("Second catch");
}
catch(java.sql.SQLDataException e) {
     System.out.println("Third catch");
}

永远不会打印出你期望的消息。

5
编译器会因此而感到烦恼。 - Luiggi Mendoza
当然可以。但真正的问题是:教练会不会更生气呢? - DigCamara
如果讲师脑子里有编译器,那就太棒了 :) - Luiggi Mendoza
2
好的,后面的异常并不总是需要更加包容;只有在某些捕获的异常是其他异常的子类时才需要(就像给出的例子一样)。 - LarsH
1
@LarsH 你说得对。我修改了我的回答以反映缺失的信息。 - DigCamara

6

异常捕获时的顺序很重要,因为有以下原因:

请记住:

  • 基类变量也可以引用子类对象。
  • e 是一个引用变量。
catch(ExceptionType e){
}
小写字母e是对被抛出(和捕获)的ExceptionType对象的引用。
你的代码为什么没有被接受呢?
重要的是要记住,异常子类必须在其超类之前。这是因为使用超类的catch语句会捕获该类型及其任何子类的异常。因此,如果子类在其超类之后,则永远不会到达子类。
此外,在Java中,无法访问的代码是一种错误。
SQLException是SQLDataException的超类。
   +----+----+----+
   | SQLException |  `e` can reference SQLException as well as SQLDataException
   +----+----+----+
          ^
          |
          |
+----+----+----+---+
| SQLDataException |   More specific 
+----+----+----+---+

如果您的代码出现错误,例如 Unreachable code请阅读注释):

try{

} 
catch(java.sql.SQLException e){//also catch exception of SQLDataException type 

}
catch(java.sql.SQLDataException e){//hence this remains Unreachable code

}

如果您尝试编译此程序,将收到一个错误消息,指出第一个catch语句将处理所有基于SQLException的错误,包括SQLDataException。这意味着第二个catch语句将永远不会执行。
正确的解决方案是反转catch语句的顺序。即如下所示:
try{

} 
catch(java.sql.SQLDataException e){

}catch(java.sql.SQLException e){

}

参考:多个Catch子句 - Grijesh Chauhan

5

SQLDataException不会被触发,因为SQLException在它们到达SQLDataException之前就会捕获任何SQL异常。

SQLDataExceptionSQLException的一个子类。


5
考虑到异常的继承层次关系:SQLDataException extends SQLException,因此如果您首先捕获“通用”的异常(即继承层次结构中最顶层的基类),则由于多态性,下面的每个异常都是相同类型,即SQLDataExceptionisaSQLException
因此,您应该按照继承层次结构自下而上的顺序捕获它们,即从子类开始一直到(通用)基类。这是因为catch子句按照您声明它们的顺序进行评估。

但是如果我的答案错误,我该如何解释这部分呢?“你能解释一下为什么顺序很重要吗?”他是在谈论我的代码还是一般情况下呢? - user2124033
谢谢提供信息!这是更改答案的正确方式吗?尝试{} catch(java.sql.SQLException | java.sql.SQLDataException e) {} - user2124033

2

Java语言规范 §11.2.3解释了这种情况:

如果一个catch子句可以捕获已检查的异常类E1,那么如果紧接着的包围try语句的前一个catch子句可以捕获E1或E1的超类,则会发生编译时错误。

通俗解释:

更一般的异常必须在特定的异常之后。 更一般的异常必须在特定的异常之后。


1

对于编译器来说,多个catch语句就像if...else if...else if...

所以,从编译器能够映射所生成的异常(直接或通过隐式类型转换)的点开始,它将不执行后续的catch语句。

为避免这种隐式类型转换,应将更通用的异常放在最后。较为具体的应该在catch语句的开头声明,而最通用的应该放在最后的catch语句中。

SQLDataException是从SQLException派生出来的,而SQLException则是从Exception中派生出来的。因此,在catch(java.sql.SQLDataException e){}这个块中,你将无法执行任何已编写的代码。编译器甚至会标记这种情况为死代码,并且不会被执行。


1
当方法中发生异常时,会检查一个特殊的方法异常表,其中包含每个catch块的记录:异常类型、开始指令和结束指令。如果异常的顺序不正确,则某些catch块将无法到达。当然,javac可以为开发人员对此表中的记录进行排序,但它没有这样做。
JVM规范:12 一个方法的异常处理程序被搜索以匹配的顺序很重要。在类文件中,每个方法的异常处理程序都存储在一个表中(§4.7.3)。运行时,当抛出异常时,Java虚拟机按照它们在相应的类文件中的异常处理程序表中出现的顺序搜索当前方法的异常处理程序,从该表的开头开始。
只要第一个异常是第二个异常的父级,第二个块就变得不可达。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接