日常写代码也经常遇到异常处理的情况,最近写异常处理语句时经常在想Java异常处理的机制原理到底是怎么样的,此前没有怎么注意这个问题,今天挑这块研究下。其中众多手头资料中发现宋红康老师做的课件里面有很多地方说的很好,所以这篇很多地方都是针对宋老师课程做的笔记整理,其中课件上几张图示也比较给力地帮助我对某一知识点的理解,所以也一并借鉴过来整到我这篇笔记里来了。
>认识异常
异常是运行时在代码序列中引起的非正常情况,是导致程序中断运行的一种指令流(开发时的语言语法错误和逻辑错误不是异常)。
所有异常类型都是内置类Throwable的子类,Throwable位于异常类层次中的顶层部。Throwable之下有两个子类Error类和Exception类,它们将异常事件分为两类:
- Error类: 该类定义了在常规环境下不希望由程序捕获的异常。次异常由Java运行时系统使用,以指示运行时环境本身出现了某些错误,通常是为了响应灾难性的失败而创建的,程序员程序通常不能处理,Java虚拟机无法解决的严重问题。如:堆栈溢出、JVM系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。
- Exception类:因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码处理。该类既可以用于用户程序应当捕获的异常情况,也可以用于创建自定义异常类型的子类。Exception有一个重要子类RuntimeException。常见异常有:无效数组索引、除零、空指针异常、试图读取不存在的文件、网络连接中断等。
发生异常一般有两种解决方法:一种是遇到错误就终止程序的运行。另一种是通过程序员编程时就考虑错误的检测、错误消息的提示,给出错误的处理。当出现引发异常的情况,就会创建用来表示异常的对象,并在引起错误的方法中抛出异常对象。
捕获异常最理想的时间是编译期间,然而有些错误只在运行时才发生,如数组下标越界、除数为0
异常种类
编译时异常 是编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。编译器要求Java程序必须捕获或声明所有编译时异常。如果程序不处理这类异常,可能带来未知的结果。
运行时异常是编译器不要求强制处置的异常。一般是编程时的逻辑错误,是程序员应积极避免其出现的异常。 java.lang.RuntimeException类及它的子类都是运行时异常。这类异常可以不做处理,因为这类异常很普遍,全处理可能会对程序的可读性和运行效率产生影响。
>异常处理机制
Java将异常处理的程序代码集中在一起,与正常的程序代码分开,采用异常处理机制处理,这样便有了异常处理的抓抛模型。
Java程序在执行过程中如出现异常,会生成一个异常类对象,该异常对象被提交给Java运行时系统,这个过程称为抛出(throw)异常。异常对象可以由虚拟机自动生成,也可以由开发人员手工创建。
如果一个方法内抛出异常,该异常对象会被抛给调用者方法中处理,如果异常没有在调用者方法中处理,它将继续被抛给这个调用方法的上层方法,以此类推,直到异常被处理。这一过程称为捕获(catch)异常。
如果一个异常回到main()方法,main()方法也不处理,则程序运行终止。程序员只能处理Exception,而对Error无能为力。
异常处理机制提供两种异常处理方法:
第一种是通过try-catch-finally语句实现,catch语句可以有多个,以处理多个异常
try{
...... //可能产生异常的代码
}
catch( ExceptionName1 e ){
...... //当产生ExceptionName1型异常时的处理办法
}
catch( ExceptionName2 e ){
...... //当产生ExceptionName2型异常时的处理办法
}
finally{
...... //无论是否发生异常,都无条件执行的语句
}
-
|- > 如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
|-> 在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
第二种是声明抛出异常,即对处理不了的异常或者要转型的异常,在方法声明处通过throws语句抛出。
public void readFile(String file) throws FileNotFoundException {
……
// 读文件的操作可能产生FileNotFoundException类型的异常
FileInputStream fis = new FileInputStream(file);
……
}
如果每个方法都是简单的抛出异常,在方法调用方法的多场嵌套中,Java虚拟机会从出现异常的方法代码中往回找,直到找到处理该异常的代码为止,将异常交给相应的catch语句处理。如果Java虚拟机找到方法调用栈的最底部main()方法时仍没有找到处理异常的代码,将调用异常对象的printStacktrace()方法,打印方法调用栈的异常信息。
如果出现异常的线程是主线程,则整个程序运行终止,如果是非主线程,则该线程终止,其他线程继续运行。
可见异常处理越早消耗的资源和时间越少,产生的影响范围越小,因此自己能处理的异常一般不要抛给调用者处理。
>异常处理5个关键字
对于这5个关键字在处理异常时需要遵守一定的使用原则
- try、catch、finally三个关键字不能单独使用,要组合成try…catch、try…finally、try…catch…finally这三种结构使用;
- 尽量避免try块过大,争取一个try块对应一个或多个异常,不提议把不会出现异常的代码放到try块里面;
- catch块尽量保持一个块捕获一类异常,捕获后的异常要么处理,要么抛出新类型的异常、要么转译;
- catch语句可以有一个或多个,但finally语句最多只能有一个。多个catch块时,Java虚拟机会匹配其中一个异常类会子类,执行这个catch块,而不执行其他catch块;
- try、catch、finally三个代码块中变量的作用域分别独立而不能相互访问。若要在三个块中都能访问,要把变量定义到这些块的外面;
- 精确异常的类型,不要不顾什么类型的异常都写成Excetion;
- 别让try…catch语句参与控制流程序流程,异常控制是为了处理程序的非正常情况;
- throw语句后不能有其他语句,因为这些语句都不会 执行;
- throw用来抛出一个异常,在方法体内。语法格式:throw异常对象;
- throws用来声明方法可能会抛出异常,在方法名后,语法格式:throws异常类型1,异常类型2…异常类型n。
- 如果一个方法调用了另外一个声明抛出异常的方法,那么这个方法要么处理异常,要么声明抛出异常。
判断一个方法是否可能出现异常,一般是方法声明时用throws语句,方法中用throw语句,方法调用的方法声明有throws关键字