Java异常
学习Java通过异常处理错误:
Java使用异常来提供一致的错误报告,使得构件能够与客户端代码可靠地沟通问题。
使用异常能够降低错误代码的处理复杂度,如果不使用异常,那么就必须检查特定的错误,并在程序中许多地方去处理它。使用了异常
就不必在方法调用处进行检查,因为异常处理机制保证能捕获这个错误,而且只需在一个地方进行处理。
- 异常情形:
阻止当前方法或者作用域继续执行的问题。在当前环境下无法获得必要的信息来解决问题,能做的就是从当前环境跳出,并且把问题交给上一级环境。
当异常抛出后:将使用new在堆上创建异常对象,当前的执行路径被终止,并且在当前环境中弹出对异常对象的引用,然后异常处理机制接管程序,并开始寻找一个适当的地方继续执行程序,适当的地方就是指异常处理程序,它使得程序从错误状态中恢复,使程序要么换一种方式运行要么继续运行下去。
异常使得我们可以将每一件事都当作一个事务来考虑。
- 异常参数
标准的异常类有两个构造器:一个默认的构造器;一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器(throw new NullPointerException(“t=null”))。
在使用了new创建异常对象后,此对象引用将传给throw,我们也可以简单地将异常处理看成一种不同的返回机制,另外我们还可以用抛出异常的方式从当前的作用域退出。
Throwable对象是异常类型的根类。错误信息可以保存在异常对象内部或者异常类的名称来暗示。
**
- 捕获异常:
**
监控区域-try块:如果方法内部抛出了异常,这个方法将在抛出异常的过程中结束,要是不希望方法就此结束,就可以在方法内设置一个特殊的块来捕获异常,以为在这个块里尝试各种方法调用。
异常处理程序-catch:抛出的异常必须得到处理,这个地点就是异常处理程序。异常处理程序必须紧跟在try块之后,当异常抛出时,异常处理机制负责搜寻参数和异常类型相匹配的第一个处理程序,然后进入catch子句执行,此时认为异常得到了处理。
异常处理理论上有两种基本模型:终止模型;恢复模型(遇到错误时不能抛出,而是调用方法来修正错误)。
- 创建自定义异常:
可以自定义异常类来表示程序中可能会遇到的特定的问题。
自定义异常类就必须从已有的异常类继承,最好选择意思相近的异常类继承(最简单的是让编译器为你产生默认的构造器)。
public class MyException extends Exception {
public MyException(String msg){
super(msg);
}
}
- 异常说明:
Java提供了相应的语法,使我们能以礼貌的方式告知客户端程序员某个方法可能会抛出的异常的类型,然后客户端程序员可以进行相应的处理。异常说明使用throws关键字,后面接一个所有潜在异常类型的列表。
void f()throws MyException,RuntimeException{
}
如果方法里代码产生了异常却没有进行处理,编译器会发现这个问题并提醒你:要么处理这个异常,要么就在异常说明中表明此方法将产生异常。
可以声明方法将抛出异而实际不抛出。在定义抽象基类和接口时这样做很重要,这样派生类或者接口实现就能抛出这些预先声明的异常。
- 捕获所有异常:
通过捕获异常类型的基类Exception就可以只写一个异常处理程序来捕获所有类型的异常。最好把他放在处理程序列表的末尾,以防它抢在其他处理程序之前捕获异常了。
- 栈轨迹:
printStackTrace()方法所提供的的信息可以通过getStackTrace()方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,每一个元素都表示栈中的一帧
- 重新抛出异常:
重抛异常会把异常抛给上一级环境中的异常处理程序,同一个try块的后续catch子句将被忽略。异常对象的所有信息都得以保持。
如果是把当前异常对象重新抛出,那么printStrackTrace()方法显示的将是原来异常抛出点的调用栈信息。要更新这个信息可以调用fillInStackTrace()方法,返回一个Throwable对象,它把当前调用栈信息填入原来的那个异常对象。
- 异常链:
想要在捕获一个异常后抛出另一个异常,并且希望把原始的异常信息保存下来,这就被称为异常链。Throwable的子类(三种基本异常类:Error;Exception;RuntimeException)在构造器中都可以接收一个cause对象作为参数,这个cause就是用来表示原始的异常,这一就可以把原始的异常传给新的异常。使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。其他类型需要用initCause()方法。
- Java标准异常:
Throwable对象可分为两种类型:Error用来标识编译时和系统错误;Exception是可以被抛出的基本类型。
- RuntimeException:
运行时异常会自动被Java虚拟机抛出,不需要在异常说明中声明方法将抛出RuntimeException,他们也被称为不受检查异常。
通常不用捕获RuntimeException异常,但还是可以在代码中抛出RuntimeException异常。
如果RuntimeException没有被捕获而直达main(),那么在程序退出前将调用异常的printStackTrace()方法。
只能在代码中忽略RuntimeException及其子类类型的异常,其他类型的异常的处理都是由编译器强制实施的。因为RuntimeException代表的是编程错误。
- 使用finally进行清理:
对于一些代码,希望无论try块异常是否抛出,他们都希望得到执行,这通常适用于内存回收之外的情况。
当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally子句。(包括:打开的文件或者网络连接、屏幕上的图形、外部世界的某一个开关)
- 异常的限制:
当覆盖方法时,只能抛出在基类方法的异常说明中列出的那些异常。
异常限制对构造器不起作用,派生类构造器的异常说明必须包含基类构造器的异常说明。派生类构造器不能捕获在基类构造器中抛出的异常。
不能基于异常说明来重载方法。
一个出现在基类方法异常说明里的异常不一定会出现在派生类的方法的异常说明里。
对于在构造阶段可能会抛出异常并且要求清理的类,最安全的方式是使用嵌套的try子句:
try {
Car car=new Car();
try {
}catch (Exception e){
throw new RuntimeException();
}finally {
//清理
}
}catch (Exception e){
throw new RuntimeException();
}
这样构造清理对象是在外层的try块中进行,如果构造失败则进入外部的catch,清理动作不会执行。如果构造成功,清理动作肯定会被执行。
- 异常匹配:
抛出异常时,异常处理系统就会按照代码书写顺序寻找最近的处理程序,查找时也不要求完全匹配,派生类对象也可以匹配其基类的处理程序:
public class Annoyance extends Exception {
}
public class Sneeze extends Annoyance {
}
public class Cleanup {
public static void main(String[] args) {
try {
throw new Sneeze();
}catch (Sneeze e){
System.out.println("Sneeze");
}catch (Annoyance e){
System.out.println("Annoyance");
}.
try {
throw new Sneeze();
}catch (Annoyance e){
System.out.println("Annoyance");
}
}
}
catch (Annoyance e)会捕获Annoyance以及所有从它派生的异常。所以建议派生类的异常处理放在基类之前。
- 异常使用指南:
- 在恰当的级别处理问题,在知道该如何处理的情况下再捕获异常;
- 解决问题并且重新调用产生异常的方法;
- 进行少许修补,然后绕过异常发生的地方继续执行;
- 用别的数据进行计算,以替代方法预计会返回的值;
- 把当前运行环境下能做的事情尽量做完,然后把相同的异常抛到更高层;
- 终止程序;
- 进行简化;
- 让类库和程序更安全;