Java异常处理机制

什么是异常

异常是指不期而至的各种状况,从而阻止当前方法或作用域继续执行。如:数组下标越界、文件找不到等等。异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。

Java通过Throwable类的众多子类描述各种不同的异常:

图1

Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,Java虚拟机(JVM)一般会选择线程终止。

Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

Exception类又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception,也就是非运行时异常 ):

  • 运行时异常:这类异常会自动被虚拟机抛出,所以不必在异常说明中把它们列出来。这类异常也被称为不受检查异常,程序中可以选择捕获处理,也可以不处理,都可以成功编译。
  • 受检查的异常:是RuntimeException及其子类以外的异常,此类异常必须在程序中捕获处理,或者抛给它的调用者,让上一层去处理该异常。否则程序不能通过编译。

对于受检查的异常必须捕捉、或者声明抛出。允许忽略RuntimeException和Error。

异常处理机制

当抛出异常后,Java将使用new在堆上创建异常对象,然后当前的执行路径被终止,并且从当前的环境中弹出对异常对象的引用。此时,异常处理机制接管程序,寻找并执行异常处理程序,异常处理程序就是捕获处理异常的地方,它的任务就是将程序从错误状态恢复,以使程序要么换一种方式运行,要么继续运行下去。

相关概念跟语句

抛出异常

当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。

捕获异常

在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。

异常处理程序

捕获处理异常通过try、catch语句完成 ,其语法为:

try {
    // 可能会发生异常的程序代码
} catch (Type1 id1){
    // 捕获并处置try抛出的异常类型Type1
}
catch (Type2 id2){
     //捕获并处置try抛出的异常类型Type2
}

try块称为监控区域,其中包含着可能发生异常的代码。Java方法在运行过程中出现异常,则创建异常对象。将异常抛出监控区域之外,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序(catch块),然后进入catch子句执行,此时认为异常得到了处理。一旦catch子句结束,则处理程序的查找过程结束,这意味着其他的catch子句不再有和被捕获异常类型匹配的机会。

try-catch语句后面还可以跟着finally子句,finally子句无论是否出现异常都会被执行,其语法为:

try {
    // 可能会发生异常的程序代码
} catch (Type1 id1) {
    // 捕获并处理try抛出的异常类型Type1
} catch (Type2 id2) {
    // 捕获并处理try抛出的异常类型Type2
} finally {
    // 无论是否发生异常,都将执行的语句块
}

finally子句也不总是会被执行,在System.exit()(终止JVM)或者当前线程死亡的情况下,finally子句不会被执行。

异常说明

当一个方法出现异常,但没有能力处理这种异常时,便可以将其抛给其调用者去处理,此时需要用到异常说明。

Java鼓励人们把方法可能会抛出的异常告知所有此方法的客户端程序员,这样做使得调用者能确切知道写什么代码可以捕获所有潜在的异常。其相应的语法为:

方法名 throws Exception1,Exception2,..,ExceptionN {    
}

异常说明属于方法声明的一部分,紧跟在形式参数列表之后,Exception1,Exception2,…,ExceptionN 为声明要抛出的异常列表。当方法抛出异常列表中的异常时,方法将不对这些类型及其子类类型的异常作处理,而抛向调用该方法的方法,由调用者去处理,如果方法调用者也处理不了,可以继续抛出异常,但最终要有能够处理该异常的调用者,否则程序将被终止。

即使方法不产生异常,也可以使用异常说明,为异常先占个位子,以后就可以抛出这种异常而不用修改已有的代码。这种用法在定义抽象类和接口时经常使用到,这样其派生类或接口实现就可以抛出这些预先声明的异常。

使用throws声明异常时,要注意:

  • 必须声明方法抛出的任何受检查异常(checked exception)。即如果一个方法出现受检查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误。

  • 若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。

void method1() throws IOException{}  //合法  

//编译错误,必须捕获或声明抛出IOException  
void method2(){  
  method1();  
}  

//合法,声明抛出IOException  
void method3()throws IOException {  
  method1();  
}  

//合法,声明抛出Exception,IOException是Exception的子类  
void method4()throws Exception {  
  method1();  
}  

//合法,捕获IOException  
void method5(){  
 try{  
    method1();  
 }catch(IOException e){…}  
}  

//编译错误,必须捕获或声明抛出Exception  
void method6(){  
  try{  
    method1();  
  }catch(IOException e){throw new Exception();}  
}  

//合法,声明抛出Exception  
void method7()throws Exception{  
 try{  
  method1();  
 }catch(IOException e){throw new Exception();}  
} 

使用throw关键字抛出异常

throw关键字用来抛出一个Throwable类型的异常。该方法会在throw语句后停止执行其正常流程,转而执行catch子句或者finally子句,甚至程序终止。
  我们知道,异常是异常类的实例对象,我们可以创建异常类的实例对象通过throw语句抛出。该语句的语法格式为:

throw new Exception;

例如抛出一个IOException类的异常对象:

 throw new IOException;

使用throw语句抛出了受检查异常,应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。

使用throw new Exception语句抛出异常,在new创建了异常对象之后,此对象的引用将传给throw。throw产生的效果类似与使用return返回,可以简单地把它看成一种不同的返回机制,使用throw还可以从当前的作用域退出,在这两种情况下,将会返回一个异常对象,然后退出作用域或方法。

注意:当一个方法同时出现了多个throw或者return语句时,后续执行的throw或者return语句会覆盖掉前面执行的throw或者return语句。

当finally子句中出现throw或者return语句时,就会出现这种现象,所以finally子句中不应该出现return语句。

例如:

package Test;

public class TestException {
    public TestException() {
    }

    boolean testEx() throws Exception {
        boolean ret = true;
        try {
            ret = testEx1();
        } catch (Exception e) {
            System.out.println("testEx, catch exception");
            ret = false;
            throw e;
        } finally {
            System.out.println("testEx, finally; return value=" + ret);
            return ret;
        }
    }

    boolean testEx1() throws Exception {
        boolean ret = true;
        try {
            ret = testEx2();
            if (!ret) {
                return false;
            }
            System.out.println("testEx1, at the end of try");
            return ret;
        } catch (Exception e) {
            System.out.println("testEx1, catch exception");
            ret = false;
            throw e;
        } finally {
            System.out.println("testEx1, finally; return value=" + ret);
            return ret;
        }
    }

    boolean testEx2() throws Exception {
        boolean ret = true;
        try {
            int b = 12;
            int c;
            for (int i = 2; i >= -2; i--) {
                c = b / i;
                System.out.println("i=" + i);
            }
            return true;
        } catch (Exception e) {
            System.out.println("testEx2, catch exception");
            ret = false;
            throw e; //抛出了异常
        } finally {
            System.out.println("testEx2, finally; return value=" + ret);
            return ret; //把上面抛出的异常覆盖了,方法正常返回
        }
    }

    public static void main(String[] args) {
        TestException testException1 = new TestException();
        try {
            testException1.testEx();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
执行结果:
i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, finally; return value=false
testEx, finally; return value=false

异常链

在捕获一个异常后抛出另一个异常,并且把原始异常的信息保存下来,这被称为异常链。

所有Throwable的子类在构造器中都可以接受一个cause(Throwable类型)对象作为参数,这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出异常,也能通过这个异常链追踪到最初异常的位置。

如果是没有提供带cause参数构造器的异常,可以使用initCause(Throwable cause)方法把其他类型的异常链接起来。

例子:

没有使用异常链:

public class ExceptionChain {

    public static void main(String[] args) {
        try {
            m2();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void m2() {
        try{
            m1();
        }
        catch (Exception e) {
            throw new RuntimeException();
        }
    }

    public static void m1() {
        throw new NullPointerException();
    }
}

控制台:

java.lang.RuntimeException
    at test.ExceptionChain.m2(ExceptionChain.java:18)
    at test.ExceptionChain.main(ExceptionChain.java:7)

使用异常链:

public class ExceptionChain {

    public static void main(String[] args) {
        try {
            m2();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void m2() {
        try{
            m1();
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void m1() {
        throw new NullPointerException();
    }
}

控制台:

java.lang.RuntimeException: java.lang.NullPointerException
    at test.ExceptionChain.m2(ExceptionChain.java:18)
    at test.ExceptionChain.main(ExceptionChain.java:7)
Caused by: java.lang.NullPointerException
    at test.ExceptionChain.m1(ExceptionChain.java:23)
    at test.ExceptionChain.m2(ExceptionChain.java:15)
    ... 1 more

通过异常链可以将受检查的异常转换成“不受检查的异常”。如果在一个方法中出现了受检查的异常,但是在此方法中不知道该如何处理这个异常,也不想打印一些无用的信息时,可以把受检查的异常屏蔽掉,不用“吞掉”异常,也不必把它放到方法的异常说明里,而且异常链还能保证你不会丢失任何原始异常的信息。

例如:

public class ExceptionChain {

    public static void main(String[] args) {
        try {
            m2();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void m2() {
        m1();
    }

    public static void m1() {
        try {
            throw new ClassCastException();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
}

控制台:

java.lang.RuntimeException: java.lang.ClassCastException
    at test.ExceptionChain.m1(ExceptionChain.java:23)
    at test.ExceptionChain.m2(ExceptionChain.java:16)
    at test.ExceptionChain.main(ExceptionChain.java:9)
Caused by: java.lang.ClassCastException
    at test.ExceptionChain.m1(ExceptionChain.java:21)
    ... 2 more

常见的异常

runtimeException子类:

  • java.lang.ArrayIndexOutOfBoundsException
    数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。

  • java.lang.ArithmeticException
    算术条件异常。譬如:整数除零等。

  • java.lang.NullPointerException
    空指针异常。例如:调用null的实例方法、访问null的属性等等。

  • java.lang.NegativeArraySizeException
    数组长度为负异常。

  • java.lang.ArrayStoreException
    数组中包含不兼容的值抛出的异常。

  • java.lang.SecurityException
    安全性异常。

  • java.lang.IllegalArgumentException
    非法参数异常。

受检查异常:

  • java.lang.ClassNotFoundException
    找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。

  • IOException
    操作输入流和输出流时可能出现的异常。

  • EOFException
    当输入过程中意外到达文件或流的末尾时,抛出此异常。

  • FileNotFoundException
    文件未找到异常。

  • ClassCastException
    类型转换异常。

  • SQLException
    数据库操作异常。

  • NoSuchFieldException
    字段未找到异常。

  • NoSuchMethodException
    方法未找到异常。

  • NumberFormatException
    字符串转换为数字抛出的异常。

  • StringIndexOutOfBoundsException
    字符串索引超出范围抛出的异常。

  • IllegalAccessException
    安全权限异常。通常是通过反射调用了private方法所导致的。

  • InstantiationException
    当应用程序试图使用Class类中的newInstance()方法创建一个类的实例,而指定的类对象无法被实例化时,抛出该异常。

异常使用指南

应该在下列情况下使用异常:

  1. 在恰当的级别处理异常。(在知道该如何处理的情况下才捕获异常。)
  2. 解决问题并且重新调用产生异常的方法。
  3. 进行少许修改,然后绕过异常发生的地方进行执行。
  4. 用别的数据进行计算,以代替方法预计会返回的值。
  5. 把当前运行环境下能做完的事情尽量做完,然后把相同的异常重抛到更高层。
  6. 把当前运行环境下能做完的事情尽量做完,然后把不同的异常抛到更高层。
  7. 终止程序。
  8. 进行简化。(如果你的异常模式使问题变得复杂,那用起来会非常痛苦也很烦人。)
  9. 让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资。)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值