异常,让程序有更好的容错性,并分离了错误处理代码与逻辑实现代码
异常处理的目的:
- 使程序代码混乱最小化
- 捕获保留异常讯息
- 通知合适的人员处理相应异常
- 采用合适的方式结束异常活动
异常处理的使用规范:
先小后大,先处理子类异常,在处理父类异常。
异常代码结构:
1 2 3 4 5 6 7
| try { } catch(ExceptionName e) { } finally { }
|
- 增强try 代码结构:
1 2 3 4 5 6 7
| try( ) { } catch(ExceptionName e) { }
|
增强try的好处是省略了finally块来处理需要关闭的资源,在try里的资源会在try块运行结束或捕获异常后关闭。
try 警官专抓异常
出行带着catch 探员和finally 探员
catch 探员负责定罪
finally 探员负责善后
catch finally 有时齐出动
有时带其中一个便够
两个都不带也行
函数签名带上throws
增强 try警官很好用
自带finally 防疏漏
不要滥用异常:
- 别try 普通的业务代码(不会抛出异常的代码)
- 别用try 取代负责业务逻辑的if 和 else 小兄弟。(杀鸡用牛刀:异常的性能比if else 差)
- 别把try 块踹成一个胖子(避免使用庞大的try块)
- 不要乱抓人!(尽量不要使用catch all语句)
(以下参考王垠的编程的智慧,非常好的一篇文章 和 《疯狂Java讲义》第十章关于异常处理)
正确使用异常:
- 道不同不相为谋,异常要各try 各的(不同的异常情况要分开try,不要潦草的用一个Exception捕获所有异常)
- 抓我要有明确证据(尽量减少try 块的内容,尽量分离与异常无关的代码)
- 抓我就要对我负责啊(调试的时候print 异常,调试完毕后要对异常进行相应处理
- 抓我也得选对地方吧(在合理的Layer 处理异常)
- 不要把我丢给不相关的人(在需要的地方进行异常转译)
案例分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Properties p = Properties(); try { p.load(new FileInputStream("sql_config.txt"); } catch(IOException e) { e.printStackTrace(); } String drv = p.getProperty("drv"); String url = p.getProperty("url"); String usr = p.getProperty("usr"); String pwd = p.getProperty("pwd"); try { Class.forName(drv); } catch(ClassNotFoundException e) { e.printStackTrace(); } try { Connection conn = DriverManager.getConnection(url, usr, pwd); } catch(SQLException e) { e.printStackTrace(); }
|
上面的例子示范了JDBC 数据库的连接过程,此处有不同的异常情况,第一个try 块里检测的是有没有sql_config.txt
这个文件,第二个try 块里检测了程序是否成功加载了SQL 驱动,第三个try 块检测了是否通过DriverManager 成功生成了Connection 实例。三个try 块分别检测了不同的异常类型,这样才达到了捕获异常的目的。千万不要用Exception 来捕获所有的异常,因为Exception 是所有异常的父类,如果一有任何异常出现就会抛出异常,而不会按代码想要catch 的异常类型检查相应异常,于是难以定位异常发生的地点以及类型,便也失去了使用异常的意义)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Properties p = Properties(); try { p.load(new FileInputStream("sql_config.txt"); String drv = p.getProperty("drv"); String url = p.getProperty("url"); String usr = p.getProperty("usr"); String pwd = p.getProperty("pwd"); Class.forName(drv); Connection conn = DriverManager.getConnection(url, usr, pwd); } catch(IOException e1) { e1.printStackTrace(); } catch(ClassNotFoundException e2) { e2.printStackTrace(); } catch(SQLException e3) { e3.printStackTrace(); }
|
上面的例子把三个try 块的内容放在一起,用catch 来捕获多个异常。这里有一部分的业务代码被放置在try 块里面,没有做到分离普通的业务代码和可能出现异常的代码,有碍迅速定位异常的位置。捕获多个异常的原理是如果try 块出现异常,运行时环境会将异常与catch 块中的异常类型逐一比较,最后抛出相应的异常类型。抛出多个异常的方法在程序不是特别复杂的情况下可以使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Properties p = Properties(); try { p.load(new FileInputStream("sql_config.txt"); String drv = p.getProperty("drv"); String url = p.getProperty("url"); String usr = p.getProperty("usr"); String pwd = p.getProperty("pwd"); Class.forName(drv); Connection conn = DriverManager.getConnection(url, usr, pwd); } catch(Exception e) { }
|
上面的代码有两个问题,一是没有对要捕获的异常类型进行明确的分类检查,这样会难以定位异常。第二点是没有对异常进行任何处理,如果发生任何异常并不进行任何处理,会极度影响系统调试,也会为系统安全和代码健壮性埋下隐患。在代码调试的阶段可以使用printStackTrace()
通过打印异常栈来debug。但是当程序完成后,要对相应的异常进行具体处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public void login(String username, String password) { String loginQuery = "select * from Users where username=? and password=?"; try { Connection conn = getConnection(); PreparedStatement pstmt = conn.prepareStatement(loginQuery); ResultSet rs = pstmt.executeQuery(); pstmt.setString(1, username); pstmt.setString(2, password); if(rs.next()) { } } catch (SQLException e) { JOptionPane.showMessageDialog(new JFrame() , "incorrect username/password" , "login error" , JOptionPane.ERROR_MESSAGE); } finally { pstmt.close(); conn.close(); } }
|
上面的代码为捕获的异常进行了具体的处理,通过弹出错误窗口的方式提醒用户操作失败。