Java异常探索
2016-06-30 00:00
417 查看
摘要: 前前后后看过很多次Java异常的这个模块,一直没有真正的理解其作用,感觉只是增加了编程的负担。随着读过书籍增多,知识的积累也有一些增长,现在重新再一次来看异常这个模块,终于有明白一些。这里特别推荐一下大师Josh Bloch的书《effective java》,在书中关于异常的这一章,真的是Java关于异常的最佳实践了。如果看了还是觉得模糊,没有关系,随着知识的积累多看几遍就会好一些,这里记一下我的总结,希望对你有所帮助。
首先,思考这样一个问题,为什么要使用异常?我对这个问题思考了很多遍,也看到一些答案,但是始终不理解,下面会通过一些实例来探索这个问题的答案。
异常,表示意外情况。异常是在方法中抛出的,具体来说,异常是在方法的前置条件不满足的情况下抛出的。例如,有一个方法public static Connection getConnection(String url) throws SQLException可以获取一个数据库连接,需要一个url参数,当方法(API)的调用者在调用这个方法的时候传递进来的url参数为空或者不合法,那么就违反了这个方法的前置条件。
现在来考虑如果没有异常程序的流程怎样执行。不管这个方法是这样实现的,因为不满足依赖,方法肯定不能完成它的功能。如果没有异常,那么API的调用者应该怎样处理呢?
Java异常设计者认为这样的处理方式让业务和错误处理的代码耦合在一起了,这不是一个好的方式。另外并不是所有的API调用者都会恰当的处理,比如不执行判断,直接使用可能造成程序直接退出,这样是完全没有恢复机会的,并且没有办法得到错误信息。
现在再一次来看使用异常的原因:
1. 让业务处理代码和错误处理代码解耦
2. 提供给API调用者相关的错误信息
3. 给API调用者相关的恢复机会
第一个原因就不说了,个人有个人的看法。
第二个原因,个人认为这是Java异常最棒的地方,Java异常提供的堆栈信息非常方便帮助定位错误,有利于程序的调试。可能是因为习惯了Java异常给我们的堆栈信息,我们才认为是理所当然的事情,试着使用一下那些异常定义使用不好的第三方jar包就知道调试没有异常的程序是一种怎样的感受了。
在说第三个原因之前,先说一个重要的概念,受检异常与非受检异常,非受检异常是只直接或间接继承自RuntimeException类的异常类。其他的异常属于受检异常。
图1 受检异常与非受检异常
受检异常可以理解为是要受到编译器检查的异常,检查什么呢?检查异常有没有被处理,意思就是受检异常必须被捕获或者申明抛出。受检异常是希望API调用者从异常中恢复。
如上面程序所示的NoSuchAlgorithmException就是一个受检异常,因为NoSuchAlgorithmException继承自GeneralSecurityException,而GeneralSecurityException继承自Exception,不是直接或者间接的继承自RuntimeException,所以NoSuchAlgorithmException是一个受检异常。
对于受检异常是必须要什么抛出,或者捕获的,这是受检异常设置的意图,希望从异常中恢复。就像上面程序所示的一样当抛出NoSuchAlgorithmException异常时,我们的恢复方法就是使用默认的md5算法。但是大多数时候我们只是希望得到错误信息,而不是从错误中恢复。这样造成的麻烦是非常多的,因为受检异常必须捕获或者什么抛出,所以就会有很多try catch或者throws申明这样的不是很优雅的代码。这对于API的调用者也绝对是一种负担。想Matin Flower和Think in Java的作者都认为受检异常的麻烦比好处多。
个人认为Java的受检异常的确不够优雅,这主要是最开始写数据连接的代码的时候一直强迫我捕获异常,并且那时候我并不知道为什么,也不知道怎样优雅的处理。不过受检异常存在的确是有用的。
如上面的程序所示,SQLException也是一个受检异常,在上面的程序中我们没有做任何的处理,我们就把当做非受检异常一样没有捕获异常。我们只是调用了这个方法10次,如下图所示,使用MySql客户端工具执行show processlist可以看到每一次调用都会建立一个连接并且没有关闭。这样迟早会造成资源耗尽的情况。
图2 数据库连接
所以对于异常的使用建议是尽量不要使用受检异常,除非特别强调需要API调用者必须执行一些清理工作。对于异常的其他最佳实践推荐《effective java》下面是一张关于异常的知识的一些整理。
图3 异常知识点整理
首先,思考这样一个问题,为什么要使用异常?我对这个问题思考了很多遍,也看到一些答案,但是始终不理解,下面会通过一些实例来探索这个问题的答案。
异常,表示意外情况。异常是在方法中抛出的,具体来说,异常是在方法的前置条件不满足的情况下抛出的。例如,有一个方法public static Connection getConnection(String url) throws SQLException可以获取一个数据库连接,需要一个url参数,当方法(API)的调用者在调用这个方法的时候传递进来的url参数为空或者不合法,那么就违反了这个方法的前置条件。
现在来考虑如果没有异常程序的流程怎样执行。不管这个方法是这样实现的,因为不满足依赖,方法肯定不能完成它的功能。如果没有异常,那么API的调用者应该怎样处理呢?
Connection con = getConnection(url); if(con != null) {正常流程} else{错误处理}
Java异常设计者认为这样的处理方式让业务和错误处理的代码耦合在一起了,这不是一个好的方式。另外并不是所有的API调用者都会恰当的处理,比如不执行判断,直接使用可能造成程序直接退出,这样是完全没有恢复机会的,并且没有办法得到错误信息。
现在再一次来看使用异常的原因:
1. 让业务处理代码和错误处理代码解耦
2. 提供给API调用者相关的错误信息
3. 给API调用者相关的恢复机会
第一个原因就不说了,个人有个人的看法。
第二个原因,个人认为这是Java异常最棒的地方,Java异常提供的堆栈信息非常方便帮助定位错误,有利于程序的调试。可能是因为习惯了Java异常给我们的堆栈信息,我们才认为是理所当然的事情,试着使用一下那些异常定义使用不好的第三方jar包就知道调试没有异常的程序是一种怎样的感受了。
在说第三个原因之前,先说一个重要的概念,受检异常与非受检异常,非受检异常是只直接或间接继承自RuntimeException类的异常类。其他的异常属于受检异常。
图1 受检异常与非受检异常
受检异常可以理解为是要受到编译器检查的异常,检查什么呢?检查异常有没有被处理,意思就是受检异常必须被捕获或者申明抛出。受检异常是希望API调用者从异常中恢复。
import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class WhyUseException { private static final String DEFAULT_ALGORITHM = "MD5"; public static void main(String[] args) { @SuppressWarnings("unused") MessageDigest md = null; md = getMessageDigest("sha-256"); } public static MessageDigest getMessageDigest(String algorithm) { MessageDigest md = null; try { return MessageDigest.getInstance(algorithm); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); try { md = MessageDigest.getInstance(DEFAULT_ALGORITHM); } catch (NoSuchAlgorithmException e1) { // e1.printStackTrace(); // 使用默认的md5,确定是存在的,所以断言不会执行到这个地方 } } return md; } }
如上面程序所示的NoSuchAlgorithmException就是一个受检异常,因为NoSuchAlgorithmException继承自GeneralSecurityException,而GeneralSecurityException继承自Exception,不是直接或者间接的继承自RuntimeException,所以NoSuchAlgorithmException是一个受检异常。
对于受检异常是必须要什么抛出,或者捕获的,这是受检异常设置的意图,希望从异常中恢复。就像上面程序所示的一样当抛出NoSuchAlgorithmException异常时,我们的恢复方法就是使用默认的md5算法。但是大多数时候我们只是希望得到错误信息,而不是从错误中恢复。这样造成的麻烦是非常多的,因为受检异常必须捕获或者什么抛出,所以就会有很多try catch或者throws申明这样的不是很优雅的代码。这对于API的调用者也绝对是一种负担。想Matin Flower和Think in Java的作者都认为受检异常的麻烦比好处多。
个人认为Java的受检异常的确不够优雅,这主要是最开始写数据连接的代码的时候一直强迫我捕获异常,并且那时候我并不知道为什么,也不知道怎样优雅的处理。不过受检异常存在的确是有用的。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class ExceptionTest { public static void main(String[] args) { for(int i =0;i<10;i++) testValidConnection(); while(true);//不让程序结束,可以在数据库中看到数据库连接并没有关闭 } public static boolean testValidConnection() { Connection connection = null; try { Class.forName("com.mysql.jdbc.Driver"); connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "tim", "123456"); Statement statement = connection.createStatement(); statement.executeQuery("select *");//错误的SQL语句,抛出异常 connection.close();//并没有执行到 } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); }finally{ // if(connection != null) // try { // connection.close(); // } catch (SQLException e) { // // ignore // } } return false; } }
如上面的程序所示,SQLException也是一个受检异常,在上面的程序中我们没有做任何的处理,我们就把当做非受检异常一样没有捕获异常。我们只是调用了这个方法10次,如下图所示,使用MySql客户端工具执行show processlist可以看到每一次调用都会建立一个连接并且没有关闭。这样迟早会造成资源耗尽的情况。
图2 数据库连接
所以对于异常的使用建议是尽量不要使用受检异常,除非特别强调需要API调用者必须执行一些清理工作。对于异常的其他最佳实践推荐《effective java》下面是一张关于异常的知识的一些整理。
图3 异常知识点整理
相关文章推荐
- Ruby中的异常处理代码编写示例
- SQL Server 2005 中使用 Try Catch 处理异常
- MySQL抛出Incorrect string value异常分析
- 浅谈C#中简单的异常引发与处理操作
- 详解C#编程中异常的创建和引发以及异常处理
- 详解JavaScript中的异常处理方法
- java程序中的延时加载异常及解决方案
- 解析Java异常的栈轨迹及其相关方法
- .NET(C#):Emit创建异常处理的方法
- windows7服务器上weblogic启动失败异常解决方法
- 有关ajax的error与后台的异常问题解决
- 深入探讨JAVA中的异常与错误处理
- GO语言异常处理机制panic和recover分析
- 浅谈JAVA 异常对于性能的影响
- 解析C++编程中的bad_cast异常
- Java中的异常测试框架JUnit使用上手指南
- C++ 异常处理 catch(...)介绍
- 详解Oracle自定义异常示例
- php中异常处理方法小结
- PHP中异常处理的一些方法整理