为什么订单在捕获exception时很重要?

我不得不用一些代码回答这个问题:

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

您正在为将使用此方法的数据库程序编写代码,并且您希望专门处理每个代码。 try / catch子句应该是什么样的?
您可以使用no-ops– empty blocks {} – 用于catch子句内容。
我们只对这里的语句的语法和结构感兴趣。

我回答说:

 try { } catch(java.sql.SQLException e) { } catch(java.sql.SQLDataException e) { } 

由于这个原因,他不接受答案:

“你的捕获条款的顺序是错误的。你能解释为什么这个命令很重要吗?”

他的回答是否正确?

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

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

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

正确答案在Grijesh的回答中

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

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

想想exception的inheritance层次结构: SQLDataException extends SQLException因此,如果首先捕获“generics”(即层次结构的最顶层基类),那么由于多态性,每个“低于”的东西都属于同一类型,即: SQLDataException isa SQLException

因此,您应该以自下而上的顺序捕获它们,即inheritance层次结构,即,子类首先一直到(generics)基类。 这是因为catch子句按照您声明它们的顺序进行评估。

SQLDataException永远不会被命中,因为SQLException会在它们到达SQLDataException之前捕获任何SQLexception。

SQLDataExceptionSQLException子类

由于以下原因,在捕获exception时订购事项:

记得:

  • 基类变量也可以引用子类对象。
  • e是参考变量
 catch(ExceptionType e){ } 

小写字符e是对抛出(和捕获) ExceptionType对象的引用。

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

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

SQLException是SQLDataException的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语句就像if..else if..else if ..

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

要避免这种隐式类型转换,您应该最后保留更通用的exception。 应该在他的捕获语句的开头说明更多的派生,并且最通用的应该在最后的捕获陈述中说明。

SQLDataException派生自SQLException,它是来自Exception的实习生。 因此,您将无法执行以catch(java.sql.SQLDataException e){}此块编写的任何代码。 编译甚至标志为该情况,它是一个死代码,不会被执行。

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

JVM规范: 1和2

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

只要第一个exception是第二个exception的父节点,第二个块就变得不可解密。

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

如果catch子句可以捕获已检查的exception类E1并且紧跟的try语句的前一个catch子句可以捕获E1或E1的超类,那么这是一个编译时错误

英文版:

更一般的例外必须在具体的例外之后。 更一般的例外必须在具体的例外之后。