异常
异常有“我对此感到意外”的含义,代表程序执行出错,专业点的描述:异常是指阻止当前方法和作用域继续执行的问题(是个问题,哈哈)
异常分类
Throwable:两个重要的子类,Error(JVM内部错误或者是资源耗尽错误),Exception(异常)
- Error是程序无法处理的错误,而Exception则是程序本身可以处理的异常
- Exception分RuntimeException和其他异常
- RuntimeException是由程序错误导致的
- 其他异常则是程序本身没有问题,但是I/O之类的错误导致的异常
按照受不受编译器检查异常分:受检查异常和非检查异常
- 非受检查异常包括:Error类和RuntimeException下的都是属于非受检查异常
- 编译器会为每个受检查异常检查是否配有异常处理器,也就是说非受检查异常没有异常处理器也是行的,至少编译器不管。
- 受检查异常,这种异常是无法预测的,所以编译器必须管,写在try catch中
常见的异常情况:
- 用户输入错误
- 设备错误
- 物理限制
- 代码错误
- 如下标越界
异常对象的生成
- 虚拟机自动生成
- 手动创建
为什么需要异常处理?
- 因为我们希望程序运行期间出错时系统能够返回一种安全状态(不能崩了),同时保存用户的操作结果
异常处理的任务:将控制权从异常产生的地方转移到能够处理异常情况的处理器上
异常处理机制的作用
- 发生异常时强制程序停止运行,并告诉我们出了什么问题,(理想情况下甚至能强制处理问题,)并回到稳定状态
发生异常时会有什么行为
- 跟其他对象创建一样,会在堆上new一个异常对象,当前执行路径会被终止,会从当前环境中弹出对异常对象的引用,此时,异常处理机制接管程序,并寻找一个恰当的地方来执行程序,这个恰当的地方即异常处理程序,它的作用是使程序继续运行,要么换一种方式运行
异常构造器
- 默认构造器
- 以String作为参数,以便能把相关信息放入异常对象的构造器(以后可以提取出来)
捕获异常
try块
- 作用:(捕获异常)在方法内部抛出异常,或者是在方法内部调用其他方法抛出了异常,那么这个方法会在抛出异常的时候结束,如果不希望该方法结束,则是可以在方法内设置一个特殊的块来捕获异常,又因为这个块总是"尝试"各种(可能产生异常的)方法调用,故称为"try"块
catch块(异常处理程序)
- 作用:(处理每个捕获异常)抛出的异常必须在某处得到处理,这个地点就是异常处理程序,针对每个要捕获的异常,都需要准备相应的异常处理程序,每个catch子句看上去都是接受一个或者是仅能接收一个特殊类型的方法
终止与恢复
- 终止模型
- JAVA和C++终止的模型,他假设抛出的异常非常关键,以至于无法返回到异常发生的地方继续执行
- 恢复模型
- 指异常处理程序的主要工作是为了修正错误,然后尝试重新调用出问题的方法,并认为第二次能成功
常见的java异常处理的方式
- try-catch-finally
- throws+异常类型
注意事项:
-
try块,如果没有try块,则是必须要准备一下finally块
-
finally块,不管有没有捕捉到异常,在try块或则是在catch块中遇到了return,finally块也会在return 之前执行,只要进入到try块就会执行finally块
创建自定义异常
class SimpleException extends Exception{
// 自定义异常类,使用默认的异常构造器
}
public class Myexception {
public static void f() throws SimpleException {
System.out.println("throw SimpleException from f()");
throw new SimpleException();//百分百产生异常,且不处理,哈哈
}
public static void main(String[] args) {
try {
f();
} catch (SimpleException e) {
// e.printStackTrace();//打印栈轨迹,将打印从异常抛出处到方法调用处,不打印也行
System.out.println("catch it");
}
// 由于有异常捕获处理,故这行能够执行
System.out.println("hello world");
}
}
// output
throw SimpleException from f()
catch it
hello world
异常与记录日志(待续)
重新抛出异常
- 为什么需要重新抛出异常?
- 我希望把异常抛给上一级环境中的处理程序
异常说明
- JAVA鼓励方法声明潜在的异常,以便让方法调用者可以捕获所有的异常
- 用throws来声明可能抛出的异常类型,void f(){…}则是表明不会抛出任何异常(除了RuntimeException继承来的异常,他们可以在没有异常声明的情况下抛出,哈哈RuntimeException真的很任性)
- 代码必须跟异常声明保持一致,如果方法里面的代码产生了异常却没有处理,编译器则会提醒你
- 要么处理这个异常
- 要么在方法中声明异常
捕获所有的异常与获取信息
- 你用Exception就行了,
- getMessage方法来获取详细信息,toString方法则是返回对Throwable的简单描述+详细信息(知道的真多)
- printStackTrace方法则是会打印Throwable对象和他的调用轨迹
//输出到标准错误
printStackTrace()
//这两个重载方法可以指定输出流
printStackTrace(PrintStream)
printStackTrace(PrintWriter)
异常链
-
为什么需要异常链?
- 如果你捕获到一个异常后希望抛出另一个异常,而且希望把原始异常信息保存下来
-
标准异常类即预定义异常类,
-
子类抛出的异常范围不能比父类大
- 如果父类方法中没有说明要抛出异常,那么重写父类方法时,你只能在子类方法中直接将异常捕获,不能throws
-
异常是Exception类型的对象,异常对象:里面封装了错误信息,异常总是抛给调用方,会抛出异常的方法必须声明他可能会这样做,用throws,
异常与构造阶段
- 对应构造阶段可能抛出异常,而且要求清理的类,最安全的方式是使用嵌套的try,(即在合适的时候才触发finally)
- JAVA的缺陷:除了内存清理外,所有的清理不会自动发生
异常匹配
- 异常处理机制会按照代码书写顺序查找最近的处理程序,找到匹配的程序后,就认为异常得到了处理,不再查找
把“受检查异常”转换成“不受检查异常”
- 可以对异常进行包装,生成新的异常
try {
// something
} catch (Exception e) {
throw new RuntimeException(e);
}