12.1 概念
“异常”这个词有“我对此感到意外”的意思。问题出现了,但在当前环境中没有足够的信息去处理它,但是你知道不应该对此视而不见,所以就可以先将这个问题提交到一个更高级别的环境中,在这里将作出正确的决定
12.2 基本异常
当抛出异常后,有几件事会发生:
1.和java中其他对象一样,会先使用new在堆上创建异常对象
2.当前的执行路劲被终止,并且从当前环境中弹出对异常对象的引用
3.异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序(这个恰当的地方就是异常处理程序)
12.2.1 异常参数
所有标准异常类都有两种构造器:
1.不含参数
2.含有一个字符串(将相关信息放入异常类)
可以用抛出异常的方式来从当前作用域中退出(我们能够抛出任何throwable对象)
12.3 捕获异常
12.3.1 try块
如果在方法内部抛出了异常,那么这个方法将会在抛出异常的过程中结束。若想继续执行下去,那就可以设置try块来捕获这个异常,因为是尝试运行。
12.3.2 异常处理程序
try{
}catch(Type1 e1){
}catch(Type2 e2){
}finally{}
catch语句(异常处理程序)接受一个异常类型参数,来获得处理try块中相应异常的信息。
当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序
12.4 创建自定义异常
继承Exception类就可以创建自定义异常(自定义异常最重要的部分就是类名)
class MyException extends Exception{}
12.4.1 异常与记录日志
可以在异常类中声明Logger对象,将trace中的信息保存到Logger中
class LoggingException extends Exception{
private static Logger logger=Logger.getLogger("LoggingException");
public LoggingException(){
StringWriter trace =new StringWriter();
printStraceStack(new PrintWriter(trace));
logger.severe(trace.toString);
}
}
Logger对象会将其输出发送到System.err
**12.5 异常说明 **
异常说明使用了附加的关键字throws,后面可以接一个异常类型列表
12.6 捕获所有的异常
通过捕获基类Exception可以捕获所有类型的异常,这种异常的捕获方式最好放在最后一个catch语句块里,以免抢在其他更详细的异常处理程序之前把异常捕获。
printStackTrace()方法可以传入参数(参数为想要输出到的I/O流)
12.6.1 栈轨迹
printStackTrace()中打印的信息可以通过getStackTrace()获得,返回的是一个数组,数组的每一个元素就是栈中的一帧(元素0是最后一个入栈的帧)。
12.6.2 重新抛出异常
通过在catch中重新抛出异常如:
catch(xxxException e){
throw e;
}
会将异常抛出到高一级的环境中,该catch语句之后的catch不会捕捉到该异常
12.6.3 异常链
常常希望捕获一个异常后抛出另一个异常,并且希望把原始异常信息保存下来,这被称为异常链,所有throwable的子类在构造器中都可以接受一个cause(因由)的对象作为参数,cause表示原始异常,通过把原始异常传递给新的异常,使得就算是在新的位置抛出了异常也能追寻到最开始抛出异常的地方
在Throwable的子类中,只有三种基本的异常类提供了带cause的构造器:
- error
- Exception
- RunTimeException
如果要把其他异常连接起来应该用initCause()方法(这是每个异常类都有的方法)
12.7 java标准异常
Throwable可分为两种类型
-
Error:编译时和系统错误
-
Exception:可以被抛出的基本类型,运行时,方法,类库等地方
12.7.1 特例:RuntimeException
运行时异常也称为“不受检查异常”这种异常属于错误,将被自动捕获
12.8 使用finally进行清理
在finally中的子句总是会执行
12.8.1 finally用来做什么
把除了内存以外的资源恢复到初始状态
12.8.2 在return中使用finally
12.8.3 缺憾:异常丢失
错误的使用finally语句块就会造成异常丢失
12.9 异常的限制
在覆盖方法时,只能抛出在基类方法的异常说明里列出的那些异常(意味着当基类的代码用到派生类时仍然可以运行),但是派生类可以抛出基类跑出的异常的子异常
一个出现在基类的异常说明,不一定会出现在派生类中,这和继承刚刚相反,异常是越来越少。
12.10 构造器
有一点很重要:时刻问自己:当异常发生时,所有东西都会被正确清理吗?
对于在构造阶段可能会抛出异常,并且要求清理的类,直接使用finally是不正确的,因为对象可能在创建时发生异常导致,对象没有被正确创建,那么在finally语句中就会错,最安全的方式是使用嵌套的try-catch子句:
这种惯用的清理方法在构造器不会抛出异常时也能使用,其基本规则就是:当创建需要清理的对象之后,立即进入一个try-finally语句块
12.11 异常匹配
在抛出异常后,异常处理机制会按照代码的书写顺序找出“最近”的处理程序,找到后就不再寻找,派生类可以匹配基类异常。
12.12 其他可选方式
“吞食则有害”:只有当你知道怎么处理异常程序时再捕获它,捕获了却不做事情是有害的
12.12.1 历史
12.12.2 观点
12.12.3 把异常传递给控制台
12.12.4 “被检查的异常”转换为“不被检查的异常”
即包装进一个try-catch语句块并用RuntimeException抛出
12.13 异常使用指南
12.14 总结