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

Java 为什么在捕获异常时顺序很重要?,java,exception-handling,Java,Exception Handling,我必须用一些代码来回答这个问题: 假设我编写了以下方法规范: public void manipulateData()抛出java.sql.SQLException、java.sql.SQLDataException 您正在为数据库编写代码 将使用此方法并希望处理每个 具体地说try/catch子句应该是什么样子? 您可以对catch子句内容使用no-ops—空块{}。 我们只对这里语句的语法和结构感兴趣 我这样回答: try { } catch(java.sql.SQLException e

我必须用一些代码来回答这个问题:

假设我编写了以下方法规范:
public void manipulateData()抛出java.sql.SQLException、java.sql.SQLDataException

您正在为数据库编写代码 将使用此方法并希望处理每个 具体地说try/catch子句应该是什么样子?
您可以对catch子句内容使用no-ops—空块{}。
我们只对这里语句的语法和结构感兴趣

我这样回答:

try {

} catch(java.sql.SQLException e) {

}
catch(java.sql.SQLDataException e) {

}
出于以下原因,他没有接受答案:

“您的catch子句顺序错误。您能解释一下为什么顺序很重要吗?”


他的回答正确吗

是的,他是对的。正如您在中所看到的,
SQLDataException
SQLException
的子类。因此,您的答案是错误的,因为它会在第二个
catch
中创建一个无法访问的代码块

在Java中,此代码甚至不会编译。在其他语言(例如python)中,这样的代码会产生一个微妙的bug,因为
SQLDataException
实际上会在第一个块中被捕获,而不会在第二个块中被捕获(如果它不是子类的话,情况就是这样)

如果您回答了catch(java.sql.SQLException | java.sql.SQLDataException e){},那么它仍然是不正确的,因为问题要求专门处理每个异常


正确答案是在Java中,您必须首先放置包含性最低的异常。下一个例外必须更具包容性(当它们相关时)

例如:如果您将最包容的(异常)放在第一位,那么将永远不会调用下一个。代码如下:

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");
}

永远不会打印您希望打印的邮件

SQLDataException
将永远不会被命中,因为
SQLException
将在到达
SQLDataException
之前捕获任何SQL异常


SQLDataException
SQLException

的一个子类,想想这些异常的继承层次:
SQLDataException扩展了SQLException
因此,如果您首先捕获“通用”类(即层次结构的最顶层基类),那么“下面”的每一个事物都是相同类型的,因为多态性,即。,SQLDataException
isa
SQLException

   +----+----+----+
   | SQLException |  `e` can reference SQLException as well as SQLDataException
   +----+----+----+
          ^
          |
          |
+----+----+----+---+
| SQLDataException |   More specific 
+----+----+----+---+

因此,您应该以自底向上的顺序捕获它们,即继承层次结构,即子类首先到达(泛型)基类。这是因为
catch
子句是按照您声明它们的顺序计算的。

对于编译器,多个catch语句类似于if..else if..else if

因此,从编译器可以映射生成的异常(直接或通过隐式类型转换)的角度来看,它不会执行后续的catch语句

为了避免这种隐式类型转换,最后应该保留更通用的异常。更多派生语句应该在catch语句的开头说明,最通用的应该在最后一个catch语句中说明


SQLDataException是从SQLException派生出来的,SQLException是实习生从Exception派生出来的。因此,您将无法执行在
catch(java.sql.SQLDataException e){}
这个块中编写的任何代码。编译器甚至会在这种情况下标记它是死代码,不会执行。

捕获异常时,顺序很重要,原因如下:

记住:

  • 基类变量也可以引用子类对象
  • e
    是一个参考变量
小写字符e是对抛出(和捕获)的
ExceptionType
对象的引用

您的代码不被接受的原因?

重要的是要记住,异常子类必须位于它们的任何超类之前。这是因为使用超类的catch语句将捕获该类型及其任何子类的异常。因此,如果子类位于其超类之后,则永远无法到达该子类。
此外,在Java中,无法访问的代码是一个错误。

SQLException
SQLDataException

   +----+----+----+
   | SQLException |  `e` can reference SQLException as well as SQLDataException
   +----+----+----+
          ^
          |
          |
+----+----+----+---+
| SQLDataException |   More specific 
+----+----+----+---+
如果您的书写出现错误
无法访问的代码
(阅读注释):

如果尝试编译此程序,将收到一条错误消息,指出第一条catch语句将处理所有基于SQLException的错误,包括SQLDataException。这意味着第二个catch语句将永远不会执行

正确的解决方案?

要修复它,请颠倒catch语句的顺序。如下所示:

try{

} 
catch(java.sql.SQLDataException e){

}catch(java.sql.SQLException e){

}

当方法中发生异常时,会检查一个特殊的方法异常表,它包含每个catch块的记录:异常类型、开始指令和结束指令。如果异常顺序不正确,则无法访问某些catch块。当然,javac可以为开发人员对该表中的记录进行排序,但它不能

JVM规范:和

方法的异常处理程序搜索匹配项的顺序很重要。在类文件中,每个方法的异常处理程序存储在表中(§4.7.3)。在运行时,当抛出异常时,Java虚拟机将按照异常处理程序在类文件中相应的异常处理程序表中出现的顺序(从该表的开头开始)搜索当前方法的异常处理程序

只要第一个异常是第二个异常的父异常,第二个异常块就变得不可检查。

§11.2.3解释了这种情况:

如果catch子句可以检查catch,则这是编译时错误 异常类E1和 封闭的try语句可以捕获E1或E1的超类。
try{

} 
catch(java.sql.SQLDataException e){

}catch(java.sql.SQLException e){

}