java异常处理机制
java异常类
java.lang.Throwable
方法:
getMessage(); 获得异常描述
printStackTrance(); 打印异常堆栈
关于Throwable
** StackTraceElement。一个final类,代表栈轨迹中的元素,一个异常可能有多个元素。
一.打印栈轨迹的方法
- 主动调用Throwable对象的printStackTrace()=printStackTrace(System.err),printStackTrace(PrintStream),printStackTrace(PrintWriter)中的其中一个。
- 如果一个Exception没有被处理,直接在main方法后面throws,程序退出前将调用异常的printStackTrace()方法,最终是Exception in thread "main" + printStackTrace()
栈轨迹
- 栈轨迹显示了"把你带到异常抛出的地点"。
- 上面三个方法调用this.getOurStackTrace()获得栈轨迹;同时Throwable提供了public的方法getStackTrace()获得栈轨迹(实际返回getOurStackTrace().clone())。
- getStackTrace()将返回栈轨迹中元素所构成的数组,其中每一个元素都是一帧。元素0是栈顶元素,栈顶元素为调用序列里的最后一个方法,栈底元素是第一个方法。
fillInStackTrace方法
- native fillInStackTrace()方法将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象儿建立的,所以返回的还是原来的异常。
- 调用此方法的那一行将成为异常新的发生地,有关原来异常发生点的信息会丢失。它的效果等价于捕获一个异常后,重新抛出另外一种异常。两者不同的 是,fillInStackTrace后的异常还是原来的异常(只是少了栈轨迹而已);而重新抛出一个异常的话,完全跟原异常信息无关了(当然也没有栈轨 迹)。
异常链
- 常常会再捕获一个异常后抛出另外一个异常,并且希望把异常原始信息保存下来,这被称为异常链。
- 在JDK1.4以前,程序员必须自己编写代码来保存原始异常信息,
- 现在所有Throwable的子类子构造器中都可以接受一个cause对象作为参数,这个cause就异常原由,代表着原始异常,即使在当前位置创建并抛出行的异常,也可以通过这个cause追踪到异常最初发生的位置。
- 只有Error,Exception,RunimeException提供了带cause参数的构造器,其他的所以异常就只有通过initCause()来设置cause了。
effective java 和think in java
think in java
- 发现错误的最佳时期是编译期间,然而编译期间并不能找出所有数据,余下的问题必须在运行期间解决。
- switch case语句里如果throw Exception的话是不需要break的。
- 异常处理理论上有两种基本模型。终止模型,java和c++支持的模型,将假设错误非常关键,一旦异常被抛出,就表明错误已经无法挽回,也不能继续执行;恢复模型,遇到错误不能抛出异常,而是调用方法来修正错误。
- 尽量不要捕获RuntimeException,但还是可以在代码中抛出RuntimeException类型的异常。
- 如果一个方法只申明了异常,但并没有在方法体抛出任何异常,调用此方法的方法是没有异常可以捕获的。
- 丢失异常:1.不使用异常链;2.从finally语句返回。
- 异常的限制:1.父类异常大于等于子类异常,子类要么不抛,要么抛出父类异常或父类异常的子异常。2.如果一个对象声明为父类引用时,调用此对象的方法, 抛出父类里声明的异常;如果一个对象声明为是自己引用时,调用此对象的方法,当然抛出自己声明的异常,跟父类无关。3.异常声明不是方法签名的一部分,方 法签名由方法名字和参数个数及类型组成,所以异常声明不能作为重载的判断标准。4.构造器可以抛出任何异常,而不必理会父类构造器抛了什么异常。
- 在创建需要清理的对象之后,立即进入try-finally块清理此对象
- 异常匹配顺序:找出最近的异常;子类的对象也可以匹配其父类的处理程序。找到匹配的后,不再继续查找。
- 异常的重要准则是只有在你知道如何处理的情况下才捕获异常。重要目标是把错误处理的代码同错误发生的地点相分离。举个列子service-dao两层,在 dao里产生了SQLException我无需处理直接抛出,到了service层,我知道要将其转换成自己的业务异常。
effective java
- 基于异常的模式:用try catch然后忽略异常的手段来达到你想要的目的。
- 异常应该只用于异常的情况下,它们永远不应该用于正常的控制流。对此我不赞同。、
- 对可恢复的情况使用受检异常,对编程错误使用运行时异常。何为可恢复的情况,是指不改变代码的基础上,这个异常情况是可以恢复的,比喻 IOExcepion可能是网络的问题,解决了网络,代码就能继续运行下去了;何为错误,当然指不能恢复的啦,比喻从你控制范围之外传递进来的null引 用,所以,此类异常都是程序员的问题,作为程序员,应该在代码中进行必要的检查。
- 异常类越少,意味着内存印迹就越小,装载这些类的时间开销也越少。
- 异常转译:高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常。异常链就是一种特殊的异常转译形式,高层异常可以通过getCause来获得低层的异常。
- 异常的细节信息应该包含所有的对该异常有贡献的参数和域的值。比喻IndexOutOfBoundsException应该包含 lowerBound,upperBound,Index三个参数。遗憾的是Java平台里并没有广泛的使用这种做法,而是全部清一色的简单继承了下父 Exception。
- 失败原子性:失败的方法调用应该使对象保持在调用前的状态。如果没有size==0的判断,可能导致size域同之前的不一致。
try catch finally break continue return
准则
finally一般情况总会执行,无论在try块里return break continue。除了调用System.exit(0)方法,该方法终止java虚拟机进程。
try块里return之前,finally会被执行
return语句会把后面的值复制到一份用来返回,如果return的是基本类型的,finally里对变量的改动将不起效果,如果return的是引用 类型的,改动将可以起效果。执行顺序:将要return时调用jsr指令----->finally代码块,finally代码块最后执行ret指 令----->继续return。
finally里的return语句会把try块里的return语句效果给覆盖掉。finally语句里的return,break,continues,抛出异常,finally子句结尾处的ret指令永远不会执行,不会在去调用finally的地方了。
我们需要做的是
- 最好把return放到方法尾而不要在try里return。
- 如果非要的话:不要在try块和finally块里都包含return;如果在try块里return, 则不要在finally块里操作被return的变量。
两个子类:
Error:仅靠程序本身无法恢复的严重错误,比如内存不足,java虚拟机的方法调用堆栈溢出。
大多数情况下,遇到这样的错误时,程序只有死的份。
StackOverError,OutOfMemoryError
Exception:表示程序可以处理的异常。程序出现异常的时候,应该尽可能地处理异常,并且
使得程序恢复运行,而不应该随意中止。
EOFException,FileNotFoundException
RuntimeException异常及其子类:运行时异常
java编译器不会检查它,这种异常没有try catch语句捕获,也没有throw字句抛出,还是编译通过。
unchecked Exception
运行时异常表示无法让程序恢复运行的异常,导致这种异常的原因通常是由于执行了错误操作。一旦出现错误操作,建议
终止程序,因此java编译器不检查这种异常。
运行时异常应该尽量避免,在程序调试阶段,遇到这种异常的时候,正确的做法是改进程序的设计和实现方式,修改程序
中的错误,从而避免这种错误。
如果捕获并且使程序恢复运行不是明智的行为:
1.这种异常一旦发生,损失严重。
2.即使程序恢复运行,也可能会导致程序的业务逻辑错乱,甚至导致更严重的异常,或者得到错误的运行结果。
受检异常,除了RuntimeException及其子类的所有Exception及其子类。
Checked Exception
这种异常要么用try..catch捕获,要么用throw子句声明抛出,否则编译不会通过。
可以处理和修复的异常,如果抛出异常的方法本身不能处理它,那么方法调用者应该去处理,如此递推,知道在某一层可以处理它,
就进行必要的处理,而不至于终止程序。
Error和java运行时异常:
相同:java编译器都不会检查他们,当程序出现错误的时候,都死。
不同:Error及其子类是由java虚拟机抛出,一般不可拓展。
RuntimeException表示程序代码中的错误,可以自定义。
异常转义和异常链
异常处理原则
1.异常只能用于非正常情况
2.为异常提供文档说明
3.尽可能地避免异常
**许多运行时异常是由于代码中的错误引起的,修改代码,改进实现方式
**提供状态测试方法,某种状态下,不适合这种操作,引起异常。
4.保持异常的原则性
**检查方法的参数是否有效,确保异常发生的时候还没有改变初始状态
**恢复代码
**在对象的临时对象上进行操作,当操作成功后,把拷贝的内容复制到原来的对象上
5.避免过于庞大的try代码块
**分段处理
6.在catch子句中指定具体的异常
try{
//code
}catch(Exception e){//code}
**俗话说对症下药,对不同的异常通常有不同的处理方式。以上代码意味着对所有异常采用相同的方式,这往往是不现实的。
**会捕获本来应该抛出的运行时异常,掩盖程序中的错误。
7.不要在catch代码块中忽略被捕获的异常
**处理异常:针对异常采取一些行动,比如弥补造成的损失或者给出警告。
**重新抛出异常:如果当前时间或者环境下还不能处理该异常,就应该重新抛出。
**进行异常转译:把原始异常包装成适合当前抽象层的另一种异常,再将其抛出。
**假如在catch代码块中不能采取任何措施,那么就不要捕获异常,而是用throws子句声明抛出异常。