1.概述
异常是程序在运行过程中产生的非正常情况,换句话说,异常就是运行时错误。在不支持异常处理的编程语言中必须手动检查错误,java避免了这个麻烦,并且在处理过程中采用面向对象的方式管理错误。
java中的异常是用来描述代码在运行过程中产生的错误及错误位置的对象。在产生异常时就会创建用来表示异常的对象,并在引起错误的方法中抛出异常对象。方法可以选择自己处理异常,也可以继续传递异常。
无论采用什么形式,在某一点都会捕获并处理异常。异常可以有java运行时系统生成,也可以通过代码手动生成。
java抛出的异常与那些违反java语言规则或java执行环境的约束的基础性错误有关。
java中的异常通过5个关键字进行管理:try、catch、throw、throws、以及finally。后续文章会逐一进行介绍
2.异常的类型
java中的异常都是内置类Throwable 的子类(注意Throwable是一个抽象类,并不是接口)。因此,Throwable位于异常类层次中的顶部。
紧随Throwable之下的是两个子类,他们将异常分为两个不同的分支。一个分支是Exctption类。这个类既可以用于用户程序异常应当捕获的情况,也可以用于创建自定义异常类型的子类。Exception有一个特别重要的子类是RuntimeException
这种类型的异常是自动定义的,包括除零异常和空指针或者数组越界等。另一个分支是Error类,该类定义了在常规环境下不希望由程序捕获的异常。Error类型的异常由java运行时系统使用,以指示运行时环境本身出现了某些错误。堆栈溢出
是这类错误的一个例子。他们通常是为了响应灾难性失败而创建的,程序一般不能处理这类错误。
3.未捕获的异常
未捕获的异常就是我们没有进行处理的异常,比如程序运行过程中出现了除零异常。这时程序会构造一个新的对象,然后抛出这个异常。这会导致执行该方法的线程终止。因为一旦抛出异常,就必须有一个异常处理程序捕获异常
并进行处理,如果没有任何程序捕获异常并进行处理则会由java运行时系统提供的默认异常处理程序捕获并进行处理。默认异常处理程序会显示一个描述异常的字符串,输出异常发生的堆栈踪迹并终止程序。(一般的输出信息都可以定位到异常)
4.try和catch
尽管运行时系统默认的异常处理程序可以帮助我们定位到异常发生的位置,但是有一些时候我们还是希望自己来处理异常,自己处理异常有两个优点:
1.允许修复错误 2.阻止程序自动终止
为了防止并处理运行时产生的错误,可以简单的在try代码块中监视被执行的代码,紧随在try代码块之后,一般都会有一个catch代码块并且指定希望捕获的异常类型。
几点注意:try代码块中的代码是可能发生错误的代码,一旦代码发生错误,则程序会跳转到相应的catch语句。try中发生错误的语句及其后面的语句都将不会被执行。换句话说catch不是“调用“,一旦执行了catch,就不可能再回到try中。
如下这段例子中,“这是异常发生之后” 这句话永远不可能被执行,因为try中发生除零异常之后,就跳转到catch而不会再继续执行try中之后的代码。
public class TestException { public static void main(String[] args) { try { System.out.println(1/0); System.out.println("这是异常之后"); }catch (Exception e) { System.out.println("发生了异常" +e); } } } //发生了异常
显示异常
Throwable类重写了Object类的toString方法,从而可以返回一个包含异常描述信息的字符串。
5.多条catch语句
有些情况下,try语句块中的代码可能会引发多种异常,将他们放在一个try中,后面可以使用多个catch语句块进行异常捕获。当try中语句产生异常时,会按顺序检查catch语句中的异常,并执行类型可以和异常进行匹配的第一个catch块,
当执行了一个catch块之后,就会忽略其他的catch块。并继续执行try/catch后面的代码。
注意:当有多条catch语句块时,异常子类一定要在异常父类之前,因为超类的异常可以捕获子类的异常。因此,如果超类异常在前的话,子类异常永远都不会被捕获到,java中认为不可达的代码是一个错误。
public class TestException { public static void main(String[] args) { try { int[] arr = new int[5]; arr[3] = 5/0; arr[10] = 6; }catch (ArithmeticException e) { System.out.println("除0异常" +e); }catch (ArrayIndexOutOfBoundsException e){ System.out.println("数组越界 "+e); }catch (Exception e) { System.out.println("抛出一个异常 "+e); } System.out.println("捕获异常之后继续执行"); } } //除0异常java.lang.ArithmeticException: / by zero //捕获异常之后继续执行
6.throw 主动抛出一个异常
之前我们说到的都是java内置的异常,程序员也可以自己抛出一个异常。首先程序员需要先定义一个异常子类继承Exception 然后使用 throw 关键字抛出这个异常。
throw 异常之后执行流会立即停止,程序会在最近的try块中查找符合条件的catch语句块。如果没有找到合适的语句块,则由运行时系统默认的异常处理进行处理。
public class TestException { public static void main(String[] args) { try { throw new MyException(); }catch (MyException e) { System.out.println("抛出一个异常 "+e); } } } class MyException extends Exception{ @Override public String toString() { // TODO Auto-generated method stub return "自己定义的异常继承Exception /"+super.toString(); } } //抛出一个异常 自己定义的异常继承Exception /com.xiaobai.exception.MyException
7.throws 关键字
有一些方法的异常我们知道可能会发生但是我们并不能进行处理,只能交给该方法的调用者处理。这时我们就要声明该方法可能会发生的异常。
除Error和RuntimeException及其子类类型之外,其他异常类型是必须要被声明的。如果不这么做就会产生运行时错误。
使用方式 : 在方法声明之后语句块之前添加throws关键字,并指明异常列表 (如果有多个,逗号分割)
public class TestException { public static void main(String[] args) { try { doSomeThing(); }catch (Exception e) { System.out.println("发生异常 "+e ); } System.out.println("捕获异常之后继续执行"); } static void doSomeThing() throws ArithmeticException,ArrayIndexOutOfBoundsException{ int[] arr = new int[5]; arr[3] = 5/0; arr[10] = 6; } }
8.finally
对于早期的java来说,一旦发生异常,由于try中的语句没有执行完毕,有可能占用的一些资源(如文件等)没有被释放,那么finally就可以用来做这些事。
finally块中的语句是指无论程序是否发生异常,都要执行的语句。如果不发生异常,程序执行完毕try后执行finally语句块;如果发生异常,程序则从try中发生异常的语句直接进入catch,当执行完catch后执行finally语句。
注意:如果方法在try/catch语句中返回调用者(不论是使用了return还是未经捕获异常还是其他情况),都会在返回语句执行之前执行finally语句块。
几个例子:
1.正常返回执行finally:(不发生异常)
public class TestException { public static void main(String[] args) { doSomeThing(); } static void doSomeThing(){ try { System.out.println("这里是try"); }catch (Exception e) { System.out.println("发生异常 "+e ); }finally { System.out.println("执行finally"); } System.out.println("这是try-catch-finally 之后"); } } //这里是try //执行finally //这是try-catch-finally 之后
2. try中返回会先执行finally
public class TestException { public static void main(String[] args) { doSomeThing(); } static void doSomeThing(){ try { System.out.println("这里是try"); return; }catch (Exception e) { System.out.println("发生异常 "+e ); }finally { System.out.println("执行finally"); } System.out.println("这是try-catch-finally 之后"); } } //这里是try //执行finally
3.catch到异常,返回之前执行finally
public class TestException { public static void main(String[] args) { doSomeThing(); } static void doSomeThing(){ try { System.out.println("这里是try"); int a = 1/0; }catch (Exception e) { System.out.println("发生异常 "+e ); return ; }finally { System.out.println("执行finally"); } System.out.println("这是try-catch-finally 之后"); } } //这里是try //发生异常 java.lang.ArithmeticException: / by zero //执行finally
9.java中常用的内置异常
9.1 未经检查异常
RuntimeException异常类及其子类异常,在所有方法的throws列表中不用包含这些异常,同时编译器也不检查方法是否抛出或者处理这些异常,称作未经检查异常。
9.2 检查异常
如果方法可能产生一个或几个异常,如果方法本身不进行处理,则必须用过throws关键字抛出该异常,称为检查异常(编译器会进行检查)