程序的实际运行的过程中经常会遇到异常情况的发生,导致正常流程的改变,造成恶劣的后果。
为了减少损失,应该事先充分预计所有可能出现的异常,然后采取对应的措施。
java异常处理机制
传统的编程语并没有异常处理机制,通常用方法特定的返回值来表示异常情况,并且正常流程和异常流程都采用相同的流程控制语句。
传统异常处理的缺点在于:
java异常处理的优点在于:
java虚拟机的方法调用栈
采用方法调用栈(method invocation stack)来跟踪每个线程中一系列的方法调用过程。
该堆栈保存了每个调用方法的本地信息(如方法的局部变量)。
每个线程都有一个独立的方法调用栈。
对于java主线程,堆栈底部是程序的入口方法main()。
当一个新方法被调用时,java就会把描述该方法的栈结构置入栈顶。
如果方法的代码块中出现异常,处理方法有两种:
(1)在当前方法中通过try...catch语句捕获并处理异常
(2)在方法的声明处通过throws语句声明抛出异常
当一个方法正常执行完毕,java虚拟机会从调用栈中弹出该方法的栈结构,然后继续处理前一个方法。
如果执行时抛出异常,则java虚拟机必须能找到捕获异常的catch代码块。
首先查看当前方法中是否存在这样的catch代码块
如果存在,那么就执行该catch代码块;否则,java虚拟机会从调用栈中弹出该方法的栈结构,继续到前一个方法中查找合适的catch代码块。
在回溯过程中,如果java虚拟机在某个方法中找到了处理改异常的代码块,则该方法的栈结构将成为栈顶元素,程序流程将转到该方法的异常处理代码部分继续执行。
当java虚拟机追溯到调用栈底部方法时,如果仍然没有找到处理该异常的代码,则会:
(1)调用异常对象的printStackTrack()方法,打印来自方法调用栈的异常信息。
(2)如果该线程不是主线程,那么终止这个线程,其他线程继续正常运行。如果该线程是主线程(即方法调用栈的底部为main()方法),那么整个应用程序被终止。
异常处理对性能的影响
抛出异常和处理异常的代码块位于同一个方法中,对性能影响小;
如果java虚拟机必须搜索调用栈来寻找处理异常的代码块,对性能影响就比较大,尤其是在调用栈底部时。
运用java异常处理机制
try...catch语句:捕获异常
try...catch的格式如下:
finally语句:任何情况下都会执行的代码
finally代码块能保证特定的操作总是会被执行。
不管try代码块是否出现异常,finally都会执行。
如果直接将finally中的语句拿出来不用finally结构,而是直接放在try...catch后面虽然也可以执行但是优缺点:
(1)与try代码的操作孤立开来,使得程序结构松散,可读性差
(2)影响程序健壮性,如果catch代码继续抛出异常,就会执行后面的语句了。
throws子句用来声明可能出现的异常
如果一个方法可能会出现异常,但是没有能力处理这种异常,可以在方法的声明处用throws子句抛出异常。
一个方法也可以声明多个异常。
throw用于抛出异常
throw抛出的异常必须是java.lang.Throwable类或者其子类的实例。
异常处理的语法规则
(1)try代码块不能脱离catch代码块或finally代码块而单独存在,try代码块后面至少有一个catch代码块或finally代码块。
(2)try代码块后面可以有零个或多个catch代码块,还可以有零个或者至多一个finally代码块。如果catch代码块和finally代码块并存,finally代码块必须在catch代码块之后。
(3)try代码块后面可以只跟finally代码块。
(4)try代码块中定义的变量的作用域为try代码块,在catch代码块和finally代码块中不能访问该变量。
如果希望在catch或者finally中访问,则必须把变量定义在try之外。
(5)当try代码块后面有多个catch代码块时,java虚拟机会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个代码快,而不会去执行其他的代码块。如果有catch代码块永远不会执行,则会报编译错误!
(6)如果一个方法可能出现异常,要么用try...catch捕获异常,要么用throws子句声明将它抛出,否则编译错误。
判断一个方法可能出现异常的依据如下:
方法中有throw语句。
调用了其他方法,其他方法用throws子句声明抛出某种异常。
(7)throw语句后面不允许紧跟其他语句,因为这些语句永远不会被执行。
异常流程的运行过程
try...catch...finally还有return和System.exit()
(1)finally语句不被执行的唯一情况是先执行了用于终止程序的System.exit()方法。java.lang.System类的静态方法exit()用于终止当前的java虚拟机进程,java虚拟机所执行的java程序也随之终止。
此外,如果在执行try代码块时,突然关掉电源,所有的进程都终止运行,也不会执行finally语句(java虚拟机都自身难保)
对异常进行响应之后还可以继续抛出异常,死死生生
(2)return语句用于退出本方法。在执行try或catch代码块中的return语句时,假如有finally代码块,会先执行finally代码块。
(3)finally虽然在return之前被执行,但finally不能通过重新给变量赋值的方式来改变return语句的返回值。
(4)建议不要在finally代码块中使用return,潜在错误包括覆盖try或catch语句和丢失异常,本来要捕获的,结果一个return过来,啥都没了。
Throwable类有两个直接子类:
Error类——表示仅靠程序本身无法恢复的严重错误
Exception类——表示程序本身可以处理的异常
IOException:操作输入流和输出流时可能出现的异常
ArithmeticException:数学异常
NullPointerException:空指针异常
IndexOutOfBoundsException:下标越界异常
ClassCastException:类型转换异常
IllegalArgumentException:非法参数异常
Exception类还可分为两种:运行时异常和受检查异常
运行时异常
RuntimeException类及其子类都被称为运行时异常,这种异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常时,即使没有用try...catch语句捕获也没有用throws抛出,还是会通过编译。
受检查异常
除了RuntimeException及其子类外,其他的Exception类及其子类都属于受检查异常(Checked Exception)。
出现这类异常要么try...catch要么throws抛出。
区分受检查和运行时异常
受检查异常表示程序可以处理的异常。
运行时的异常表示无法让程序恢复运行的异常。
区分运行时异常和错误
用户定义异常
在处理异常时,捕获原始异常,再抛出新的异常,这种处理异常的办法称为异常转译。
异常转译使得异常类型与抛出异常的对象的类型位于相同的抽象层。
异常链机制是指把原始异常包装为新的异常类,即在新的异常类中封装了原始异常类,有助于查找产生异常的根本原因。
处理多样化异常
本来是一次只能抛出一个异常的,现在是要一次抛出多个异常。
异常的处理原则
1.异常只能用于非正常情况
2.为异常提供说明文档
3.尽可能避免异常
4.保持异常的原子性
5.避免过于庞大的try代码块
6.在catch子句中指定
7.不要在catch代码块中忽略被捕获的异常