Java异常处理规则
前面介绍了使用异常处理的优势、便捷之处,本节将进一步从程序性能优化、结构优化的角度给出异常处理的一般规则。成功的异常处理应该实现如下 4 个目标。
下面介绍达到这种效果的基本准则。
熟悉了异常使用方法后,程序员可能不再愿意编写烦琐的错误处理代码,而是简单地抛出异常。实际上这样做是不对的,对于完全已知和普通的错误,应该编写处理这种错误处理代码,增加程序的健壮性;只有对外部的、不能确定和预知的运行时错误才使用异常。
下面我们来看《Java五子棋游戏》一节中处理用户输入坐标点已有棋子的两种方式。
如果将上面的处理机制改为如下方式:
异常处理机制的效率比正常的流程控制效率差,所以不要使用异常处理来代替正常的程序流程控制。例如,对于以下代码:
正确的做法是,把大块的 try 块分割成多个可能出现异常的程序段落,并把它们放在单独的 try 块中,从而分别捕获并处理异常。
实际上,Catch All 语句不过是一种通过避免错误处理而加快编程进度的机制,应尽量避免在实际应用中使用这种语句。
对异常进行合适的修复,然后绕过异常发生的地方继续执行;或者用别的数据进行计算,以代替期望的方法返回值;或者提示用户重新操作等。总之,对于 Checked 异常,程序应该尽量修复。
- 使程序代码混乱最小化。
- 捕获并保留诊断信息。
- 通知合适的人员。
- 采用合适的方式结束异常活动。
下面介绍达到这种效果的基本准则。
不要过度使用异常
不可否认,Java 的异常机制确实方便,但滥用异常机制也会带来一些负面影响。过度使用异常主要有以下两个方面:- 把异常和普通错误混淆在一起,不再编写任何错误处理代码,而是以简单地抛出异常来代替所有的错误处理。
- 使用异常处理来代替流程控制。
熟悉了异常使用方法后,程序员可能不再愿意编写烦琐的错误处理代码,而是简单地抛出异常。实际上这样做是不对的,对于完全已知和普通的错误,应该编写处理这种错误处理代码,增加程序的健壮性;只有对外部的、不能确定和预知的运行时错误才使用异常。
下面我们来看《Java五子棋游戏》一节中处理用户输入坐标点已有棋子的两种方式。
// 如果用户试图下棋的坐标点已有棋子了 if (!gb.board[xPos - 1][yPos - 1].equals("╋")){ System.out.println ("您输入的坐标点已有棋子了,请重新输入"); continue; }上面这种处理方式检测到用户试图下棋的坐标点已经有棋子了,立即打印一条提示语句,并重新开始下一次循环。这种处理方式简洁明了,逻辑清晰,提高运行效率。
如果将上面的处理机制改为如下方式:
// 如果用户试图下棋的坐标点己经有棋子了,程序自行抛出异常 if (!gb.board[xPos - 1][yPos - 1].equals ("╋")) { throw new Exception ("您试图下棋的坐标点已经有棋子了"); }上面的处理方式没有提供有效的错误处理代码,当程序检测到用户试图下棋的坐标点已经有棋子时,并没有提供相应的处理,而是简单的抛出了一个异常。这种处理方式虽然简单,但 Java 运行时接收到这个异常后,还需要进行相应的 catch 块来捕获该异常,所以运行效率要差一些。而且用户下棋重复这个错误完全是预料的,所以程序完全可以针对该错误提供相应的处理,而不是抛出异常。
异常处理机制的效率比正常的流程控制效率差,所以不要使用异常处理来代替正常的程序流程控制。例如,对于以下代码:
// 定义一个字符串数组 String[] arr = { "Hello", "Java", "Spring" }; // 使用异常处理来遍历arr数组的每个元素 try { int i = 0; while (true) { System.out.println(arr[i++]); } } catch (ArrayIndexOutOfBoundsException ae) { }运行上面程序确实可以实现遍历 arr 数组元素的功能,但这种写法可读性较差,而且运行效率也不高。程序完全有能力避免产生 ArrayIndexOutOfBoundsException 异常,程序“故意”制造这种异常,然后使用 catch 块去捕获该异常,这是不应该的。将程序改为如下形式肯定要好得多。
String arr[] = {"Hello","Java","Spring"}; for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); }异常只应该用于处理非正常的情况,不要使用异常处理来代替正常的流程控制。对于一些完全可预知,而且处理方式清楚的错误,程序应该提供相应的错误处理代码,而不是将其笼统地称为异常。
不要使用过于庞大的try块
很多初学异常机制的读者喜欢在 try 块里放置大量的代码,这样看上去“很简单“,但这种”简单“只是一种假象,因为 try 块里的代码过于庞大,业务过于复杂,就会造成 try 块中出现异常的可能性大大增加,从而导致分析异常原因的难度也大大增加。而且当 try 块过于庞大时,就难免在 try 块后紧跟大量的 catch 块才可以针对不同的异常提供不同的处理逻辑。同一个 try 块后紧跟大量的 catch 块则需要分析它们之间的逻辑关系,反而增加了变成复杂度。正确的做法是,把大块的 try 块分割成多个可能出现异常的程序段落,并把它们放在单独的 try 块中,从而分别捕获并处理异常。
避免使用 Catch All 语句
所谓 Catch All 语句指的是一种异常捕获模块,它可以处理程序发生的所有可能异常。例如,如下代码片段:try { // 可能引发Checked异常的代码 } catch (Throwsble t) { // 进行异常处理 t.printStackTrace(); }不可否认,每个程序员都曾经用过这种异常处理方式,但在编写关键程序时就应避免使用这种异常处理方式。这种处理方式有如下两点不足之处。
- 所有的异常都采用相同的处理方式,这将导致无法对不同的异常分情况处理,如果要分情况处理,则需要在 catch 块中使用分支语句进行控制,这是得不偿失的做法。
- 这种捕获方式可能将程序中的错误、Runtime 异常等可能导致程序终止的情况全部捕获到,从而“压制”了异常。如果出现了一些“关键”异常,那么此异常也会被“静悄悄”地忽略。
实际上,Catch All 语句不过是一种通过避免错误处理而加快编程进度的机制,应尽量避免在实际应用中使用这种语句。
不要忽略捕获到的异常
不要忽略异常,既然已捕获到异常,那 catch 块理应处理并修复这个错误。catch 块整个为空,或者仅仅打印出错信息都是不妥的。程序出了错误,所有的人都看不到任何异常,但整个应用可能已经彻底坏了,这是最可怕的事情。对异常进行合适的修复,然后绕过异常发生的地方继续执行;或者用别的数据进行计算,以代替期望的方法返回值;或者提示用户重新操作等。总之,对于 Checked 异常,程序应该尽量修复。
所有教程
- C语言入门
- C语言编译器
- C语言项目案例
- 数据结构
- C++
- STL
- C++11
- socket
- GCC
- GDB
- Makefile
- OpenCV
- Qt教程
- Unity 3D
- UE4
- 游戏引擎
- Python
- Python并发编程
- TensorFlow
- Django
- NumPy
- Linux
- Shell
- Java教程
- 设计模式
- Java Swing
- Servlet
- JSP教程
- Struts2
- Maven
- Spring
- Spring MVC
- Spring Boot
- Spring Cloud
- Hibernate
- Mybatis
- MySQL教程
- MySQL函数
- NoSQL
- Redis
- MongoDB
- HBase
- Go语言
- C#
- MATLAB
- JavaScript
- Bootstrap
- HTML
- CSS教程
- PHP
- 汇编语言
- TCP/IP
- vi命令
- Android教程
- 区块链
- Docker
- 大数据
- 云计算