在下面这段代码中,你能够迅速找出异常处理的六个问题吗?
1 OutputStreamWriter out = ...
2 java.sql.Connection conn = ...
3 try { // ⑸
4 Statement stat = conn.createStatement();
5 ResultSet rs = stat.executeQuery(
6 "select uid, name from user");
7 while (rs.next())
8 {
9 out.println("ID:" + rs.getString("uid") // ⑹
10 ",姓名:" + rs.getString("name"));
11 }
12 conn.close(); // ⑶
13 out.close();
14 }
15 catch(Exception ex) // ⑵
16 {
17 ex.printStackTrace(); //⑴,⑷
18 }
反例之一:丢弃异常 代码:15行-18行。
这段代码捕获了异常却不作任何处理,可以算得上Java编程中的杀手。调用一下printStackTrace算不上“处理异常”,只对调试程序有帮助。那么,应该怎样改正呢?主要有四个选择:
1、处理异常。针对该异常采取一些行动,例如修正问题、提醒某个人或进行其他一些处理,要根据 具体的情形确定应该采取的动作。
2、重新抛出异常。处理异常的代码在分析异常之后,认为自己不能处理它,重新抛出异常也不失为 一种选择。
3、把该异常转换成另一种异常。大多数情况下,这是指把一个低级的异常转换成应用级的异常。
4、不要捕获异常。
结论一:既然捕获了异常,就要对它进行适当的处理。不要捕获异常之后又把它丢弃,不予理睬。 反例之二:不指定具体的异常 代码:15行。
catch语句表示我们预期会出现某种异常,而且希望能够处理该异常。异常类的作用就是告诉Java编译器我们想要处理的是哪一种异常。再来看看前面的代码例子。我们真正想要捕获的异常是SQLException,这是JDBC操作中常见的异常。另一个可能的异常是IOException,因为它要操作OutputStreamWriter。显然,在同一个catch块中处理这两种截然不同的异常是不合适的。如果用两个catch块分别捕获SQLException和IOException就要好多了。这就是说,catch语句应当尽量指定具体的异常类型,而不应该指定涵盖范围太广的Exception类。
另一方面,除了这两个特定的异常,还有其他许多异常也可能出现。例如,如果由于某种原因, executeQuery返回了null,该怎么办?答案是让它们继续抛出,即不必捕获也不必处理。实际上,我们不 能也不应该去捕获可能出现的所有异常,程序的其他地方还有捕获异常的机会??直至最后由JVM处理。
结论二:在catch语句中尽可能指定具体的异常类型,必要时使用多个catch。不要试图处理所有可能 出现的异常。
反例之三:占用资源不释放 代码:3行-14行。
异常改变了程序正常的执行流程。这个道理虽然简单,却常常被人们忽视。如果程序用到了文件、 Socket、JDBC连接之类的资源,即使遇到了异常,也要正确释放占用的资源。为此,Java提供了一个简化 这类操作的关键词finally。
结论三:保证所有资源都被正确释放。充分运用finally关键词。
反例之四:不说明异常的详细信息 代码:3行-18行。
仔细观察这段代码:如果循环内部出现了异常,会发生什么事情?我们可以得到足够的信息判断循环 内部出错的原因吗?不能。
printStackTrace的堆栈跟踪功能显示出程序运行到当前类的执行流程,但只提供了一些最基本的信 息,未能说明实际导致错误的原因,同时也不易解读。
因此,在出现异常时,最好能够提供一些文字信息,例如当前正在执行的类、方法和其他状态信息, 包括以一种更适合阅读的方式整理和组织printStackTrace提供的信息。
结论四:在异常处理模块中提供适量的错误原因信息,组织错误信息使其易于理解和阅读。
反例之五:过于庞大的try块 代码:3行-14行。
经常可以看到有人把大量的代码放入单个try块,实际上这不是好习惯。这种现象之所以常见,原因 就在于有些人图省事,不愿花时间分析一大块代码中哪几行代码会抛出异常、异常的具体类型是什么。一些新手常常把大量的代码放入单个try块,然后再在catch语句中声明Exception,而不是分离各个可能出现异常的段落并分别捕获其异常。这种做法为分析程序抛出异常的原因带来了困难,因为一大段代 码中有太多的地方可能抛出Exception。
结论五:尽量减小try块的体积。
反例之六:输出数据不完整 代码:7行-11行。
不完整的数据是Java程序的隐形杀手。较为理想的处置办法是向输出设备写一些信息,声明数据的不完整性;另一种可能有效的办法是,先缓冲要输出的数据,准备好全部数据之后再一次性输出。
结论六:全面考虑可能出现的异常以及这些异常对执行流程的影响。
改写后的代码
OutputStreamWriter out = ...
java.sql.Connection conn = ...
try {
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery("select uid, name from user");
while (rs.next()){
out.println("ID:" + rs.getString("uid") + ",姓名: " + rs.getString("name"));
}
}catch(SQLException sqlex){
out.println("警告:数据不完整");
throw new ApplicationException("读取数据时出现SQL错误", sqlex);
} catch(IOException ioex){
throw new ApplicationException("写入数据时出现IO错误", ioex);
}finally{
if (conn != null) {
try {
conn.close();
}catch(SQLException sqlex2){
System.err(this.getClass().getName() + ".mymethod - 不能关闭数据库连接: " + sqlex2.toString());
}
}
if (out != null) {
try {
out.close();
} catch(IOException ioex2){
System.err(this.getClass().getName() + ".mymethod - 不能关闭输出文件" + ioex2.toString()); }
}
}