为什么订单在捕获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中看到的, SQLDataException
是SQLException
的子类。 因此,您的答案是错误的,因为它会在第二个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。
SQLDataException
是SQLException
的子类
由于以下原因,在捕获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的超类,那么这是一个编译时错误 。
英文版:
更一般的例外必须在具体的例外之后。 更一般的例外必须在具体的例外之后。