Java中的异常处理

来源:互联网 发布:2007办公软件下载 编辑:程序博客网 时间:2024/06/01 09:13

总结下Java中的异常处理,基本来自Java编程思想和真实的归宿的博文,如有错误或者不足,欢迎交流讨论。

1 什么是异常处理,为什么需要异常处理

异常(Exception)即程序的意外情况,C及早期的语言的错误处理往往是预定俗成的,并不是语言的一部分,语言本身不强制程序员检查和处理异常,如果每次调用方法都要检查错误,代码将变得难以阅读,且开发的效率无疑要大大降低,出错后要检查特定的错误,并在多处处理,这对于检查和处理都很不方便,对于构造大型、健壮、可维护的程序而言,这种错误处理方式显然不适合。对于有异常处理的语言而言,异常处理是程序强制程序员检查并处理异常的,异常的处理方法有两种,要么恢复到正常情况,要么终止程序并给出错误的描述(异常所在的函数、异常类型等)以帮助程序员检查错误。

2 Java异常的结构层次和分类

2.1 异常的结构层次

Java中所有的异常类均继承自Throwable类(java.lang.Throwable,可参考HTML格式的java文档),内置的异常类大部分都在java.lang包里,还有一些在util、io、net等包里。如果Java内置异常不能满足用户的需求,可以自定义异常,即创建自己的异常类。Throwable包含两个重要子类,Exception和Error。
1. Exception
表示是程序本身可以处理的异常,有两个分支,RuntimeException和IOException,前者是程序错误导致的异常,后者是程序本身没有错误,是由IO这类问题导致的异常。RuntimeException及其子类表示“JVM常用操作”引发的错误,进行Java程序设计应该重点关注RuntimeException,出现RuntimeException一定是程序员引发的
2. Error
Erro(错误)是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与程序员无关,而表示代码运行时JVM出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
这里写图片描述

2.2 异常的分类

  1. 未检查异常,又称不可查异常(unchecked exceptions),派生于Error类和RuntimeException类的所有异常,编译器不要求程序员处理,将被自动捕获,RuntimeException由JVM抛出(但是还是可以在代码中抛出RuntimeException),Error《java编程思想》上没写(但core java上写了),应该也是JVM来处理。
  2. 可检查异常,又称已检查异常(checked exceptions),异常中除了Error和RuntimeException的异常,在编译时强制检查,需要程序员来处理。

3 异常处理的方式

异常处理有两种基本模型:终止模型,这种错误非常关键,以至程序无法返回到异常发生的地方继续执行,异常一旦抛出,就表明错误无法挽回;恢复模型,希望遇见错误时就不能抛出异常,而是调用方法来修正错误,或者,把try块放在while循环里(循环设为永真,若恢复,则break),不断进入try块,直到满意的结果。

3.1 抛出异常

当一个方法内出现异常时,方法内又没有捕获异常的代码,那么就必须将异常抛出。在方法形列表后使用throws关键字给出潜在的异常类型列表(异常说明),在方法内使用throw关键字抛出异常,但是有异常说明不一定就要抛出异常,在定义抽象类和接口时很有用。

package exception;/** * 抛出异常 * @author 小锅巴 * @date 2016年3月31日下午4:17:28 * http://blog.csdn.net/xiaoguobaf */class MyException extends Exception{    public MyException(){}    public MyException(String msg){        super(msg);    }    //再次说明,在一个.class文件中可以有两个main方法,运行时选择就是,这里还是为了方便测试    public static void main(String[] args) throws Exception{//有异常声明可以不抛出异常        System.out.println("throw nothing !");    }}class MyException01 extends Exception{}public class FullConstructor {    public static void f() throws MyException{        System.out.println("Throwing MyException form f()");        throw new MyException();    }    public static void g() throws MyException{        System.out.println("Throwing MyException form g()");        throw new MyException("Originated in g()");    }    //可以在异常列表里声明将抛出多个类型的异常,但是实际上只能抛出一个,因为抛出一个程序就终止了,    public static void main(String[] args) throws MyException, MyException01{        throw new MyException01();//抛出异常//      System.out.println("Test");//抛出异常后程序就终止了,属于终止模型,eclipse提示:Unreachable code    }}/**输出:第一个main函数:throw nothing !第二个main函数:Exception in thread "main" exception.MyException01                    at exception.FullConstructor.main(FullConstructor.java:31) */

特例是RuntimeException,不用在异常说明里给出异常类型列表,其输出被报告给了System.err,并在方法退出前调用printStackTrace()

package exception;/** * 特例,RuntimeException不需要异常说明 * @author 小锅巴 * @date 2016年4月1日下午2:10:10 * http://blog.csdn.net/xiaoguobaf */public class TestRuntimeException {    static void f(){        throw new RuntimeException("From f()");    }    static void g(){        f();    }    public static void main(String[] args) {        g();    }}/** * 输出:    Exception in thread "main" java.lang.RuntimeException: From f()    at exception.TestRuntimeException.f(TestRuntimeException.java:12)    at exception.TestRuntimeException.g(TestRuntimeException.java:15)    at exception.TestRuntimeException.main(TestRuntimeException.java:18) */

3.2 捕获异常

监控区域(guarded region):一段能产生异常的代码,一般包含在try块中,在try块中的异常即被捕获了,try中可以“尝试”各种方法调用(可能会产生异常),在try块中发生异常程序将不会结束;try块后面跟着处理异常的代码(异常处理程序),一般包含在catch块中(如果没有catch块,则必须要有finally块)。

3.2.1 捕获多个异常

一个try捕获多个异常类型,并对不同类型的异常做出不同的处理,一旦和其中的任何一个catch相匹配,则catch块执行结束后即视为异常处理完,不再执行后面的catch块,这与switch case不同。

package exception;/** * 捕获多个异常 * @author 小锅巴 * @date 2016年4月1日下午2:37:19 * http://blog.csdn.net/xiaoguobaf */class Exception01 extends Exception{}class Exception02 extends Exception{}class Exception03 extends Exception{}public class CatchMultipleException {    public static void main(String[] args) throws Exception01,Exception02,Exception03{        int i = 0;        while(true){//恢复模型,不停try,直到不出现异常            i++;            try{                if(i == 1)                    throw new Exception01();                else if(i == 2)                    throw new Exception02();                else if(i == 3)                    throw new Exception03();            }catch(Exception01 e1){                System.out.println("Throw Exception01");            }catch(Exception02 e2){                System.out.println("Throw Exception02");            }catch(Exception03 e3){                System.out.println("Throw Exception03");            }            if(i == 4){            System.out.println("No exceptions, quit loop!");            break;            }        }    }}/** * 输出:    Throw Exception01    Throw Exception02    Throw Exception03    No exceptions, quit loop! */

捕获异常时,虽然catch块中不一定用到异常类型,但catch中的异常类型不可缺省。此外还可捕获所有异常,在一个catch块中捕获多个异常,那么显然类型是多个异常的基类类型,不过要放在异常处理程序的最后面,否则他后面的对到导出类型的异常处理程序将被忽略。

3.2.2 try-catch-finally

finally的使用:不论异常是否抛出,finally字句总能被执行;就算有return在finally之前,finally子句还是会得到执行;涉及break和continue,finally子句还是会得到执行。一般情况下不论try块里面发生什么,finally子句都会得到执行。当要把除内存之外的资源恢复到它们的初始状态时,就需要用到finally块,如文件、IO等。
以下情况下finally中的子句不会执行:
(1)在finally语句块中发生了异常。
(2)在前面的代码中用了System.exit()退出程序。
(3)程序所在的线程死亡。
(4)关闭CPU。

package exception;/** * finally子句 * 说明:Exception01,Exception02,Exception03已经在CatchMultipleException.java中定义了,他们在同一个包下 * @author 小锅巴 * @date 2016年4月1日下午5:04:30 * http://blog.csdn.net/xiaoguobaf */public class Finally {    public static void main(String[] args) throws Exception01,Exception02,Exception03{        int i = 0;        while(true){//恢复模型,不停try,直到不出现异常            i++;            try{                if(i == 1)                    throw new Exception01();                else if(i == 2)                    throw new Exception02();                else if(i == 3)                    throw new Exception03();                else if(i == 4){                    System.out.println("No exception, continue!");                    continue;                }                else if(i == 5){//                  System.out.println("No exception, break !");//                  break;                    System.out.println("No exception, return !");                    return;                }            }catch(Exception01 e1){                System.out.println("Throw Exception01");            }catch(Exception02 e2){                System.out.println("Throw Exception02");            }catch(Exception03 e3){                System.out.println("Throw Exception03");            }            finally{                System.out.println("In finally clause");            }        }    }}/** * 输出:Throw Exception01In finally clauseThrow Exception02In finally clauseThrow Exception03In finally clauseNo exception, continue!In finally clauseNo exception, break !In finally clause将break相关的注释掉,执行return,则输出:Throw Exception01In finally clauseThrow Exception02In finally clauseThrow Exception03In finally clauseNo exception, continue!In finally clauseNo exception, return !In finally clause */

3.2.4 重新抛出异常与异常链

先说下栈轨迹(stack trace):printStackTrace(),该方法返回一个由栈轨迹中的元素所构成的数组,第一个数组元素即是栈顶元素,是方法调用序列中的最后一个方法调用,最后一个数组元素是栈底元素,是方法调用的第一个方法调用。其中的元素可由getStackTrace()方法来访问。

重新抛出异常:捕获异常后再重新抛出异常,会把异常抛给上一级环境中的异常处理程序,此时同一个try块后面的catch子句将被忽略,若只是把当前异常对象重新抛出,那么printStackTrace()显示的还是原来异常抛出点的调用栈信息,而非重新抛出点的信息,想要更新抛出的调用栈信息,可调用fillInStackTrace()方法,它返回Throwable对象

package exception;/** * 重新抛出异常 * @author 小锅巴 * @date 2016年4月1日下午7:43:23 * http://blog.csdn.net/xiaoguobaf */public class Rethrowing {    public static void f() throws Exception{        throw new Exception("thrown form f()");//异常参数,在异常构造中    }    public static void g() throws Exception{        try {            f();        } catch (Exception e) {            System.out.println("Inside g(),e.printStackTrace()");            e.printStackTrace(System.out);//异常信息会被发送给System.out输出流            throw e;//重新抛出异常,没有使用fillInStackTrace(),故还是原抛出点的异常信息        }    }    public static void h() throws Exception{        try {            f();        } catch (Exception e) {            System.out.println("Inside h(),e.printStackTrace()");            e.printStackTrace(System.out);            //使用fillInStackTrace(),覆盖了原抛出点的异常信息,            throw (Exception)e.fillInStackTrace();//fillInStackTrace()返回的是Throwable类型的异常,与异常说明类型不相符,所以要向下转型        }    }    public static void main(String[] args) {        try{            g();        }catch(Exception e){            System.out.println("main g(): printStackTrace()");            e.printStackTrace(System.out);        }        try {            h();        } catch (Exception e) {            System.out.println("main h(): printStackTrace()");            e.printStackTrace(System.out);        }    }}/**输出:Inside g(),e.printStackTrace()java.lang.Exception: thrown form f()    at exception.Rethrowing.f(Rethrowing.java:10)    at exception.Rethrowing.g(Rethrowing.java:14)    at exception.Rethrowing.main(Rethrowing.java:33)main g(): printStackTrace()java.lang.Exception: thrown form f()    at exception.Rethrowing.f(Rethrowing.java:10)    at exception.Rethrowing.g(Rethrowing.java:14)    at exception.Rethrowing.main(Rethrowing.java:33)Inside h(),e.printStackTrace()java.lang.Exception: thrown form f()    at exception.Rethrowing.f(Rethrowing.java:10)    at exception.Rethrowing.h(Rethrowing.java:23)    at exception.Rethrowing.main(Rethrowing.java:39)main h(): printStackTrace()java.lang.Exception: thrown form f()    at exception.Rethrowing.h(Rethrowing.java:28)    at exception.Rethrowing.main(Rethrowing.java:39) */

异常链:若捕获异常之后抛出另外一种异常,原来异常发生点的信息会丢失;若要在捕获一个异常后抛出另一异常,并且希望把原来异常的信息保存下来。只有Error、Exception、RuntimeException这三种基本的异常类(不包括子类)提供了带cause的构造器,其他类型的异常若要链接,应该使用initCause()方法。

package exception;/** * 异常链 * @author 小锅巴 * @date 2016年4月1日下午7:59:19 * http://blog.csdn.net/xiaoguobaf */class ExceptionChain extends Exception{    public static void f() throws Exception{        throw new Exception();    }}public class ChainingExceptions {    public static void main(String[] args) throws Exception{        try {            ExceptionChain ec = new ExceptionChain();            ec.f();        } catch (Exception e) {            e.initCause(new NullPointerException());//iniCause()方法参数里面的异常的类型即是重新抛出后的异常类型            throw e;        }    }}/**输出:Exception in thread "main" java.lang.Exception    at exception.ExceptionChain.f(ChainingExceptions.java:10)    at exception.ChainingExceptions.main(ChainingExceptions.java:17)Caused by: java.lang.NullPointerException    at exception.ChainingExceptions.main(ChainingExceptions.java:19) */

4 Java常见异常

在Java中提供了一些异常用来描述经常发生的错误,对于这些异常,有的需要程序员进行捕获处理或声明抛出,有的是由Java虚拟机自动进行捕获处理。Java中常见的异常类:

  1. runtimeException子类:
    1、 java.lang.ArrayIndexOutOfBoundsException
    数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
    2、java.lang.ArithmeticException
    算术条件异常。譬如:整数除零等。
    3、java.lang.NullPointerException
    空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等
    4、java.lang.ClassNotFoundException
    找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
    5、java.lang.NegativeArraySizeException 数组长度为负异常
    6、java.lang.ArrayStoreException 数组中包含不兼容的值抛出的异常
    7、java.lang.SecurityException 安全性异常
    8、java.lang.IllegalArgumentException 非法参数异常

  2. IOException
    IOException:操作输入流和输出流时可能出现的异常。
    EOFException 文件已结束异常
    FileNotFoundException 文件未找到异常

  3. 其他
    ClassCastException 类型转换异常类
    ArrayStoreException 数组中包含不兼容的值抛出的异常
    SQLException 操作数据库异常类
    NoSuchFieldException 字段未找到异常
    NoSuchMethodException 方法未找到抛出的异常
    NumberFormatException 字符串转换为数字抛出的异常
    StringIndexOutOfBoundsException 字符串索引超出范围抛出的异常
    IllegalAccessException 不允许访问某类异常
    InstantiationException 当应用程序试图使用Class类中的newInstance()方法创建一个类的实例,而指定的类对象无法被实例化时,抛出该异常

5 使用异常要注意的几点

5.1异常丢失

若不恰当地finally子句,将导致异常被忽略,如:

package exception;import javax.management.RuntimeErrorException;/** * 异常的丢失 * @author 小锅巴 * @date 2016年4月1日下午8:26:53 * http://blog.csdn.net/xiaoguobaf */class VeryImportantException extends Exception{    public String toString(){        return "A verry important exception!";    }}class HoHumException extends Exception{    public String toString(){        return "A trivale exception";    }    //测试在finally中使用return    public static void main(String[] args){        try {            throw new RuntimeException();        }finally{            return;//finally中没有处理抛出异常,而是返回,执行该main函数什么也不会输出        }    }}public class LostException {    void f() throws VeryImportantException{        throw new VeryImportantException();         }    void g() throws HoHumException {        throw new HoHumException();    }    public static void main(String[] args) {        try {            LostException le = new LostException();            try{//异常监控区域的嵌套                le.f();            }finally{//没有catch块,使用finally子句                le.g();//不恰当地使用finally子句,导致下面的catch块不会执行,VeryImportantException被忽略            }        } catch (Exception e) {            System.out.println(e);        }    }}/**输出:A trivale exception */

5.2 异常的限制

因为面向对象中的继承,将父类使用的代码应用到其子类时,是能够正常工作的,正是因为此,当一个类继承子某个异常类时,子类中处理的异常不会像继承一样增加信息,而是不能增加(增加能处理的异常类),因为如果增加了,那么在父类中本来不会抛出异常,能正常工作,将父类中该代码应用到子类时,因为增加,抛出了异常,该代码就不能工作了,这违背了继承代码复用的原则。尤其是一个类实现一个接口并继承自一个类,父类和接口有同名方法,该方法异常的抛出要谨慎。具体例子就不写了,java编程思想271页有。

5.3 异常匹配

前面说了一点,即捕获所有异常,子类的对象也可以匹配父类的处理程序,还是继承的代码复用原则,能处理父类的,子类肯定也能处理,但注意要放在最后,否则其后面的子类的处理程序将被忽略。

5.4 构造器中的异常

若构造器中抛出了异常,清理行为也许就不能正常工作,因为在构造器内部对象还没有成功创建。
对于构造阶段可能抛出异常并要求清理的类(应该是文件、IO之类的,毕竟是资源,否则浪费了),最安全的方式是使用嵌套的try子句,这里直接贴java编程思想的图吧,因为作者貌似使用了自己的库。
这里写图片描述
我只想说一点,创建完对象立马就清理掉,那干嘛要创建呢?所以,我的理解是,如果有构造器中可能发生异常并又必须清理(资源),那么加一个标志位,通过判断标志位可以在成功创建时使用该对象,然后调用dispose清理对象,这样就可以确保创建成功时使用对象并清理,不成功时直接清理。

参考资料:
1. Java编程思想
2. 真实的归宿的博文
3. Java核心技术

0 0
原创粉丝点击