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

8 回答 8

42

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

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

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

正确答案在Grijesh 的回答中

于 2013-03-01T22:28:00.527 回答
22

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

永远不会打印您希望它打印的消息。

于 2013-03-01T22:28:32.487 回答
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){

}
于 2013-03-02T12:13:23.140 回答
5

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

SQLDataException是一个SQLException

于 2013-03-01T22:29:51.813 回答
5

考虑异常的继承层次结构:SQLDataException extends SQLException因此,如果您首先捕获“通用”一个(即层次结构的最顶层基类),那么由于多态性,它“下面”的所有事物都是相同的类型,即SQLDataException isa SQLException

因此,您应该在继承层次结构中以自下而上的顺序捕获它们,即子类首先一直到(通用)基类。之所以如此,是因为这些catch子句是按照您声明它们的顺序进行评估的。

于 2013-03-01T22:36:30.537 回答
2

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

如果一个 catch 子句可以捕获已检查的异常类 E1,并且紧邻的 try 语句的前面的 catch 子句可以捕获 E1 或 E1 的超类,则这是一个编译时错误。

纯英文版:

更一般的例外必须在特定例外之后。更一般的例外必须在特定例外之后。

于 2013-03-08T08:31:43.920 回答
1

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

JVM 规范:12

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

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

于 2013-03-07T10:16:41.853 回答
1

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

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

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

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

于 2013-03-02T06:07:22.250 回答