异常处理

来源:互联网 发布:软件的可维护性 编辑:程序博客网 时间:2024/05/20 06:36

概述

异常:是一个在执行期发生的事件,它中断了正在执行的程序的正常指令流。(运行时期发生的不正常情况。)
在java中用类的形式对不正常的情况进行了描述和封装对象。描述不正常的类,就称为异常类。
异常就是java通过面向对象的思想将问题封装成了对象,用异常类对其进行描述。这样可以将正常流程代码和问题处理代码分离,提高阅读性。
不同的问题用不同的类进行具体的描述。比如角标越界,空指针等等。
异常体系:异常的种类很多,意味着描述的类也很多,将其共性向上抽取,形成了异常体系。该体系的特点就是在于throwable及其所有的子类都具有可抛性。
Throwable:无论是error还是异常,问题发生就应该可以抛出,让调用者知道并处理。Throwable(可抛出)是所有的异常共同的祖先。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。
可抛性到底指的什么呢?怎么体现可抛性?
throws throw,凡是可以被这两个关键字所操作的类和对象都具备可抛性。
Throwable分成了两大重要的子类:
1.一般不可处理的:Error
特点:是由jvm抛出的严重性的问题。这种问题一般不针对性处理。错误发生时,Java虚拟机(JVM)一般会选择线程终止,直接修改程序。
2.可以处理的:Exception

异常的分类(包括Exception和Error):
1.编译时被检测异常(checked exceptions):只要是Exception和其子类都是,除了特殊子类RuntimeExecption体系。这种问题一旦出现,希望在编译时就进行检测,让这种问题有对应的处理方式。当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
2.编译时不检测异常(运行时异常):就是Exception中的RuntimeException和其子类。这种问题的发生,无法让功能继续,运算无法进行,更多的是因为调用者的原因导致的或者引发了内部状态的改变导致的。这种问题一般不处理,直接编译通过,在运行时让调用者调用时的程序强制停止,让调用者对代码进行修正。
RuntimeException:是那些可能在java虚拟机正常运行期间抛出的异常的超类。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。
该体系的特点:子类的后缀名都是用其父类名作为后缀,阅读性很强。

自定义异常

使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。
在程序中使用自定义异常类,大体可分为以下几个步骤。
(1)创建自定义异常类。
(2)在方法中通过throw关键字抛出异常对象。
(3)如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
(4)在出现异常方法的调用者中捕获并处理异常。
自定义异常时,要么继承Exception,要么继承RuntimeException。

class NegativeIndexException extends Exception//自定义的异常类{    NegativeIndexException()    {}    NegativeIndexException(String msg)    {        super(msg);//父类构造方法    }}

异常处理机制

在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。
对于运行时异常、错误或可查异常,Java技术所要求的异常处理方式有所不同:
由于运行时异常的不可检测性,为了更合理、更容易地实现应用程序,Java规定,运行时异常将由Java运行时系统自动抛出,允许应用程序忽略运行时异常。
对于方法运行中可能出现的Error,当运行方法不欲捕获时,Java允许该方法不做任何抛出声明。因为,大多数Error异常属于永远不能被允许发生的状况,也属于合理的应用程序不该捕捉的异常。
对于所有的可检测异常,Java规定:一个方法必须捕捉,或者声明抛出方法之外。也就是说,当一个方法选择不捕捉可查异常时,它必须声明将抛出异常。
能够捕捉异常的方法,需要提供相符类型的异常处理器。所捕捉的异常,可能是由于自身语句所引发并抛出的异常,也可能是由某个调用的方法或者Java运行时 系统等抛出的异常。也就是说,一个方法所能捕捉的异常,一定是Java代码在某处所抛出的异常。简单地说,异常总是先被抛出,后被捕捉的。

捕获异常

Java语言的异常捕获结构由try、catch和finally3个部分组成。其中try语句存放的是可能发生异常的Java语句,catch程序块在try语句之后,用来激发被捕获的异常;finally语句块是异常处理结构的最后执行部分,无论try语句的代码如何退出,都将执行finally语句块。
具体的格式是:

try{    //需要被检测异常的代码}catch(异常类 变量)//该变量用于接收发生的异常对象{    //处理异常的代码}finally{    //一定会被执行的代码}

例一:数组角标越界异常的捕获(其实这个异常捕获没有意义,只是作为一个例子)

class Demo{    public void show(int index)    {        if(index<0)            throw new ArrayIndexOutOfBoundsException("角标越界");        int[] arr=new int[3];        return arr[index];    }}class ExceptionDemo{    public static void main(String[] args)    {        Demo d=new Demo();        try        {            int num =d.show(-3);            System.out.println("num="+num);        }        catch (ArrayIndexOutOfBoundsException e)        {            System.out.println(e.toString());            return;            //System.exit(0);//退出jvm,这时候finally中的语句不执行        }        finally//通常用于关闭(释放)资源。        {            System.out.println("finally");        }        System.out.println("over");    }}

try catch finally代码块组合特点:
1.try catch finally
2.try catch(多个)当没有必要资源需要被释放时,可以不用定义finally
3.try finally 异常无法直接catch处理,但是资源需要关闭(在数据库中应用较多),如以下的伪代码:

            void show() throw Exception            {                try                 {                        开启资源                    throw new Exception();                    关闭资源//出异常了关闭不了了                }                finally                {                    //关闭资源                }            }

以下四种特殊情况,finally块不会被执行:
1、在finally语句块中发生了异常;
2、在前面的代码中使用了System.exit()退出程序;
3、程序所在线程死亡;
4、关闭CPU。

抛出异常

若某个方法可能会发生异常,但不想在当前方法中处理这个异常,则可以用throws、throw关键字在方法中抛出异常。
使用throws关键字抛出异常
throws关键字通常被使用在方法声明处,用来指定方法可能抛出的异常。多个异常可以使用逗号分隔。如果抛出的是Exception异常类型,则该方法被声明为抛出所有的异常。
使用throws关键字将异常抛给调用者(上一级)后,如果调用者不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的调用者。如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。如果抛出的是Error或RuntimeException,则该方法的调用者可选择处理该异常。

class Demo{    public int method(int[] arr,int index)throws NegativeIndexException//定义方法并抛出异常给调用者:主函数    {        if(index<0)        {            throw new NegativeIndexException("角标不能为负");        }        return arr[index];    }}class ExceptionDemo{    public static void main(String[] args) throws NegativeIndexException//主函数将问题抛给了jvm    {        int[] arr=new int[3];        Demo d=new Demo();        int num=d.method(arr,-3);        System.out.println("num"+num);        System.out.printn("over");    }}

Throws抛出异常的规则:
1) 如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
2)必须声明方法可抛出的任何可查异常(checked exception)。即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误
3)仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出。
4)调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。
使用throw关键字抛出异常
throw关键字通常用于方法体中,并且抛出一个异常对象。程序在执行到throw语句时立即终止,它后面的语句都不执行。通过throw抛出异常后,如果想在上一级代码中来捕获并处理异常,则需要在抛出异常方法中使用throws关键字在方法中指明要抛出的异常;如果要捕捉throw抛出的异常,则必须使用try-catch语句块。
throw通常用来抛出用户自定义的异常。
例二:自定义异常,b并用throw和throws抛出异常,再用try-catch-finally来处理异常:

class BluescreenException extends Exception{    BluescreenException(String msg)    {        super(msg);    }}class WateringException extends Exception{    WateringException(String msg)    {        super(msg);    }}class PlandelayException extends Exception{    PlandelayException(String msg)    {        super(msg);    }}class Computer{    private int state =0;    public void run()throws BluescreenException,WateringException    {        if(state==1)            throw new BluescreenException("电脑蓝屏");        if(state==2)            throw new WateringException("电脑进水");        System.out.println("running");    }    public void reset()    {        state=0;        System.out.println("电脑重启");    }}class Teacher{    private String name;    private Computer comp;    Teacher(name)    {        this.name=name;        comp=new Computer();    }    public void prelect() throws PlandelayException    {        try        {            comp.run();            System.out.println("Teaching java");        }        catch(BluescreenException e)        {            comp.reset();            prelect();        }        catch(WateringException e)        {            System.out.println(e.toString());            test();            //可以对电脑进行维修            //throw e;//并不是捕获到什么异常就抛出什么异常            //异常转换            throw new PlandelayException("课时进度无法完成,原因:"+e.getMessage())        }    }    public void test()    {        System.out.println("Let students do homework");    }}class ExceptionTest{    public static void main(String[] args)    {        Teacher t=new Teacher("MISS li");        try        {            t.prelect();        }        catch(PlandelayException e)        {            System.out.println(e.toStrig()+"......");            System.out.println("change a teacher");        }    }}

异常转换另一例子的伪代码:

class NoAddException extends Exception{    、}void addData(Data d)throws NoAddException{    连接数据库    try    {        添加数据  出现异常SQLException();    }    catch(SQLException e)    {        处理代码.        throw new NoAddException();    }    finally    {        关闭数据库    }   }

throws和throw的区别:
1、throws使用在函数上(函数声明);
throw使用在函数内。
2、throws抛出的是异常类,可以抛出多个异常,用逗号隔开;
throw抛出的是异常对象。

异常处理的原则:
1,函数内容如果抛出需要检测的异常,那么函数上必须要声明。
否则必须在函数内用try catch捕捉,否则编译失败。
2,如果调用到了声明异常的函数,要么try catch,要么throws,否则编译失败。
3,什么时候catch?什么时候throws?
功能内容可以解决,用catch;解决不了,用throws告诉调用者,由调用者解决。
4,一个功能如果抛出了多个异常,那么调用时,必须有对应的多个catch进行针对性的处理。内部有几个需要检测的异常,就抛出几个异常,抛出几个异常,就catch几个。

异常的注意事项:
1、子类在覆盖父类方法时,父类的方法如果抛出了异常,那么子类的方法只能抛出父类的异常或者该异常的子类。(或者不抛出,这是因为java语法固定的。)
2、如果父类抛出多个异常,那么子类只能抛出父类异常的子集。
简单说就是:子类覆盖父类只能抛出父类的异常或者子类或者子集。
注意:如果父类的方法没有抛出异常,那么子类覆盖时绝对不能抛,就只能try。