Java异常
Java将异常分为两种,Checked异常和Runtime异常,Java认为Checked异常都是可以在编译阶段被处理的异常,所以强制程序必须处理所有的Checked异常,而Runtime异常无需处理。Checked异常可以提醒程序员需要处理所有可能发生的异常,但Checked异常也给编程带来了一些繁琐之处。
异常处理机制
当程序运行出现意外情形时,系统会自动生成一个Exception对象来通知程序,从而实现将”业务功能实现代码”和”错误处理代码”分离,提供更好的可读性。
使用try-catch捕获异常
1 | try { |
如果没有找到处理异常的catch块,程序就会直接退出。
异常类的继承体系
Error错误一般指与虚拟机相关的严重问题,应用程序通常无法处理,无需捕获。
1 | public class DivTest |
异常捕获时,一定要记住先捕获小异常,再捕获大异常
Java 7提供的多异常捕获
- 捕获多种类型的异常时,多种异常类型之间用竖线(|)隔开。
- 捕获多种类型的异常时,异常变量有隐式的final修士,因此程序不能对异常变量重新赋值。
1 | public class MultiExceptionTest |
访问异常信息
- getMessage(): 返回该异常的详细描述字符串
- printStackTrace(): 将该异常的跟踪栈信息输出到标准错误输出
- printStackTrace(PrintStream s): 将该异常的跟踪栈信息输出到制定输出流
- getStackTrace(): 返回该异常的跟踪栈信息
1 | public class AccessExceptionMsg |
使用finally回收资源
Java的垃圾回收机制不会回收任何物理资源(数据库连接,网络连接,磁盘文件等),这些物理资源都必须显式回收。
异常处理语法结构中只有try快是必须的,但catch和finally块至少出现其中之一,也可以同时出现,且finally快必须位于所有catch块之后。如果直接抛出异常就可以不需要catch和finally。
1 | public class FinallyTest |
除非在try块,catch块中调用了推出虚拟机的方法,否则不管在try,catch中执行了怎样的代码,是否抛出异常,是否return,异常处理的finally块总会被执行。
不要在finally块中使用如return或throw等导致方法终止的语句,一旦在finally块中使用了return或throw语句,会导致try块,catch块中的return,throw语句失效
1 | public class FinallyFlowTest |
当Java程序执行try块,catch块时遇到return或throw语句,这两个语句都会导致该块的执行立即结束,但系统并不会立即停止执行return或throw,而是先去寻找异常处理流程是否包含finally块,如果没有finally块,程序立即执行return或throw语句;如果有finally,系统立即执行finally块,只有finally块执行完成后,系统才会再次跳回来执行try块,cath块里的return或throw语句。如果finally块里也使用了return或throw,在方法在finally块就会终止,不会再执行try,catch中的return,throw代码。
异常处理的嵌套
最好不要使用超过两层的嵌套异常处理。
Java 7的自动关闭资源的try语句
为了保证try语句可以正常自动关闭资源,这些资源实现类必须实现AutoCloseable或者Closeable借口,也就是其中的close()方法。
1 | public class AutoCloseTest |
Checked异常和Runntime异常体系
Checked异常必须处理,两种处理方式:
- 当前方法明确如何处理该异常,使用try…catch块捕获异常,然后修复。
- 当前方法不知道如何处理这种异常,应该在定义该方法时声明抛出该异常。
使用throws声明抛出异常
1 | public class ThrowsTest |
一旦使用throws语句声明抛出该异常,程序就无需使用try…catch块来捕获该异常。
使用throws声明抛出异常时有一个限制,就是方法重写时”两小”中的一条规则:子类方法声明抛出的异常应该是父类方法声明抛出的异常的相同或子类,子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。
1 | public class OverrideThrows |
在大部分时候推荐使用Runntime异常,而不使用Checked异常。尤其当程序需要自行抛出异常时,使用Runntime异常将更加简洁。
当使用Runtime异常时,程序无需在方法中声明抛出Checked异常,一旦发生了自定义作物,程序只管抛出Runtime异常即可。
如果程序需要在合适的地方捕获异常并对异常进行处理,则一样可以使用try…catch块来捕获Runtime异常。
使用throw抛出异常
1 | public class ThrowTest |
自定义异常类
在通常情况下,程序很少会自行抛出系统异常,因为异常的类名通常包含了该异常的有用信息,所以在选择抛出异常时,应该选择合适的异常类,从而可以明确描述该异常情况。在这种情形下,应用程序常常需要抛出自定义异常。
用户自定义异常都应该继承Exception基类,如果希望自定义个Runtime异常,则应该继承RuntimeException基类。定义异常类时通常需要提供两个构造器:一个是无参数的构造器,另一个是带一个字符串参数的构造器,这个字符串将作为该异常对象的描述信息。(getMessage()).
1 | public class AuctionException extends Exception |
如果需要自定义个Runtime异常,只需要将AuctionException.java程序中的Exception
基类改为RuntimeException基类,其他地方无需修改。
在大部分情况下,创建自定义异常都可采用与AuctionException.java相似的代码完成,只需改变AuctionException异常的类名即可,让该异常类的类名可以准确描述该异常。
catch和throw同时使用
在更复杂的情况,单靠某个方法无法完全处理该异常,必须由几个方法协作才可完全处理。也就是说,在异常出现的当前方法中,程序只对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应该再次抛出异常,让该方法的调用者再次捕获处理。
1 | public class AuctionTest |
异常链
把原始异常信息隐藏起来,仅向上提必要的异常提示信息的处理方式,可以抱枕底层异常不会扩散到表现层,避免向上暴露太多的实现细节。
这种把捕获一个异常然后接着抛出另一个异常,并把原始异常信息保存下来是一种典型的链式处理方式,称为异常链。
1 | public class SalException extends Exception |
Java的异常跟踪栈
异常对象的printStackTrace()方法用于打印异常的跟踪栈信息,根据printStackTrace()方法的输出,开发者可以找到异常的源头,并跟踪异常一路触发的过程。
1 | class SelfException extends RuntimeException |
1 | public class ThreadExceptionTest implements Runnable |
异常处理规则
不要过度使用异常
对于完全已知的错误,应该编写处理这种错误的代码。只有对于外部的,不能确定和预支的运行时错误才使用异常。
异常处理的机制初衷是将不可预期的异常的处理代码和正常的业务逻辑处理代码分离,因此觉不要使用异常处理来代替正常的业务逻辑判断。
不要使用过于庞大的try块
正确的做法是,把大块的try块分割成多个可能出现异常的程序段落,并把它们放在单独的try块中,从而分别捕获并处理异常。
避免使用Catch All语句
所谓catch all语句指的是一种异常捕获模块,它可以处理程序发生的所有可能异常。
不要忽略捕获到的异常
catch整个块为空,或者仅仅打印出出错信息都是不妥的。采取适当操作:
- 处理异常。如果知道如何处理,则采取相应措施
- 重新抛出新异常。把当前运行环境能做的做完,然后包装异常并抛出。
- 如果当前层不清楚如何处理,则不要在当前层使用catch语句,而是直接throws声明抛出该异常。