异常处理

正文

异常是什么?Java如何描述异常?

  • 异常,顾名思义就是指程序执行过程中出现的不正常情况。例如:

class ExceptionDemo {
    public static void main(String[] args) {
        int[] arr = new int[3];
		System.out.println(arr[3]);    // java.lang.ArrayIndexOutOfBoundsException
    }
}

通过编译运行上面的代码,我们发现异常发生在运行时期。

  • 在Java中用类的形式对这些不正常情况进行了描述和封装,这些类就称为异常类。其实异常就是Java通过面向对象的思想将程序中出现的问题封装成了对象。

异常体系?

程序中可能会出现的问题有很多,比如:角标越界、空指针等,那么这些问题就需要用不同的类进行具体的描述。将这些类的共性进行向上抽取就形成了异常体系。

从上面的图我们可以看到:Throwable是Java异常体系的顶级类,它的下面有两个子类:Exception和Error。

  • Throwable,顾名思义,该异常体系所有的子类包括Throwable都具有可抛性。具有可抛性的原因就在于:无论是哪种异常情况,一旦发生就应该"抛出"让调用者知道并处理。可抛性具体是通过两个关键字:throw和throws来体现的。

  • Exception:指可以处理的异常情况。

  • Error:指一般不可处理的异常情况。因为这类问题是由JVM抛出的严重问题,所以这种问题一旦发生一般不做针对性处理,建议直接修改程序。

异常处理过程?throw?

  • 那么异常到底是如何进行处理的呢?我们借助下面这个例子来进行解释:

class Demo {
    public void method(int[] arr, int index) {
        System.out.println(arr[index]);     // 2
        System.out.println("method run"); 
    }
}

class ExceptionDemo {
    public static void main(String[] args) {
        int[] arr = new int[3];
		Demo d = new Demo();
		d.method(arr, 3);        // 1
    }
}

// 一:当代码执行到2时,程序出现异常,而Java针对这种异常情况已经提前做好了描述,即是:ArrayIndexOutOfBoundsException。于是,JVM就会在此处将该异常信息封装成对应异常对象并使用"throw关键字"将该对象抛给调用者:throw new ArrayIndexOutOfBoundsException(index)。注意:这个操作是由JVM自动完成。

// 二:由于method()是被ExceptionDemo类的主函数调用,所以该对象被抛给ExceptionDemo类的主函数。

// 三:由于ExceptionDemo类的主函数并未对该异常情况进行处理,所以该异常对象继续被ExceptionDemo类的主函数向上抛给JVM。

// 四:JVM收到此异常对象之后就会启动JVM的默认异常处理机制,即是:将该异常对象的信息直接全部打印到控制台。

通过上面代码的运行结果我们还能看出:当程序发生了异常之后,该程序就被终止了,也就是说异常有终止函数的功能。

  • 既然JVM自动使用"throw关键字"抛出异常,那我们当然也可以手动执行这个操作。就像下面这样:

class Demo
{
    public int method(int[] arr, int index)
    {
        if(arr == null)
            throw new NullPointerException("数组的引用不能为空!");   // 可以自定义异常信息

        if(index >= arr.length)
        {
            throw new ArrayIndexOutOfBoundsException("数组的角标越界!"+index);
        }
        if(index < 0)
        {
            throw new ArrayIndexOutOfBoundsException("数组的角标不能为负数!:"+index);
        }
        return arr[index];
    }
}

class  ExceptionDemo
{
    public static void main(String[] args)
    {
        int[] arr = new int[3];

        Demo d = new Demo();
        int num = d.method(null, -30);
        System.out.println("num=" + num);
        System.out.println("over");
    }
}

自定义异常?throws?

对于角标为负数的情况,我们可以用"负数角标异常"来表示,但是这种异常在Java中并没有被定义和描述过。我们可以按照Java异常的面向对象思想,将负数角标这种异常情况进行自定义描述,并将其封装成对象。如下:


class FuShuIndexException extends Exception    // 自定义负数角标异常
{
    FuShuIndexException()
    {
    }

    FuShuIndexException(String msg)
    {
        super(msg);
    }
}

要注意当我们在方法中抛出该异常时,需要进行捕捉(下面会提到)或声明(使用关键字"throws")。声明的原因是:这样调用者在调用该方法之前就可以预先有一些处理方式:


// 此处省略FuShuIndexException类,与上同

class Demo
{
    public int method(int[] arr, int index) throws FuShuIndexException {   // 使用"throws关键字"声明异常
        if(arr == null)
            throw new NullPointerException("数组的引用不能为空!");

        if(index < 0)
        {
            throw new FuShuIndexException("角标不能为负数!");
        }
        return arr[index];
    }
}

class  ExceptionDemo3
{
    public static void main(String[] args) throws FuShuIndexException
    {
        int[] arr = new int[3];

        Demo d = new Demo();
        int num = d.method(null,-30);
    }
}

我们其实可以注意到上面的NullPointerException并没有进行捕捉或声明,其实通过查看jdk文档,我们发现:NullPointerException是RuntimeException的子类。异常除了根据上面的异常体系划分之外,还可以划分为运行时异常和编译时异常。那么这两者有什么区别呢?

  • 编译时异常(又称为编译时被检测异常):具体指的是Exception和除了RuntimeException体系的其他子类。这种异常在编译时就会进行检测从而让这种问题有对应的处理方式,所以这样的问题一般都可以针对性地处理。

  • 运行时异常(又称为编译时不检测异常):具体指的就是Exception中的RuntimeException和其子类。这种异常在编译时一般不处理直接通过,而在运行时让程序强制停止,表明调用者应该对代码进行修正。因为这种问题的发生会导致程序无法继续执行,它更多是由于调用者或者引发了内部状态的改变而导致的。

异常捕捉?

上面说过当我们抛出自定义异常FuShuIndexException时,需要进行捕捉或声明。异常的捕捉其实就是一种可以对异常进行针对性处理的方式。所以如果我们对FuShuIndexException进行捕捉就是下面这样:


// 此处省略FuShuIndexException类,与上同

class Demo
{
    public int method(int[] arr, int index) throws FuShuIndexException
    {
        if(index < 0)
            throw new FuShuIndexException("角标变成负数啦!");    // 1

        return arr[index];
    }
}

class  ExceptionDemo
{
    public static void main(String[] args)
    {
        int[] arr = new int[3];
        Demo d = new Demo();
        try     // 对抛出的FuShuIndexException异常进行针对性处理         
        {
            int num = d.method(arr, -1);
            System.out.println("num=" + num);
        }
        catch (FuShuIndexException e)   // 捕捉FuShuIndexException异常    // 2
        {
            e.printStackTrace();    // 3
        }
    }
}


// 同样的,我们对上面代码的异常处理过程进行分析:
// 一:当代码执行到1时,程序出现异常,JVM就在此处将该异常信息进行了封装并抛给了调用者ExceptionDemo的主函数:throw new FuShuIndexException("角标变成负数啦!"); 

// 二:由于ExceptionDemo的主函数针对该异常已经进行了针对性的处理,于是代码执行到2,JVM将该异常对象赋值给了e,即:FuShuIndexException e = new FuShuIndexException("角标变成负数啦!"); 

// 三:接下来对该异常对象进行针对性的处理:即执行3处的代码。

我们需要注意:如果程序中的方法抛出了多个异常,那么在调用该方法时,必须有对应的多个catch块进行针对性的处理。即:代码内部有几个需要检测的异常,就抛几个,抛出几个,就有几个catch块。就像下面这样:


// 此处省略FuShuIndexException类,与上同

class Demo
{
    public int method(int[] arr, int index) throws FuShuIndexException
    {
        if(arr == null)
            throw new NullPointerException("数组的引用不能为空!");

        if(index < 0)
            throw new FuShuIndexException();

        return arr[index];
    }
}

class  ExceptionDemo
{
    public static void main(String[] args)
    {
        int[] arr = new int[3];
        Demo d = new Demo();
        try
        {
            int num = d.method(null,-1);
            System.out.println("num=" + num);
        }
        catch(NullPointerException e)   // 捕捉NullPointerException异常
        {
            System.out.println(e.toString());
        }
        catch (FuShuIndexException e)   // 捕捉FuShuIndexException异常
        {
            e.printStackTrace();
        }
    }
}

由于异常体系的原因,当有多个catch块的时候,父类的catch块放在最下面。因为多个catch块按照定义顺序依次执行。

关于使用"throws"和"try-catch",我们需要注意以下问题:

  • 如果函数中的内容抛出了编译时异常,那么该函数上要么使用"throws"进行声明要么使用"try-catch"进行捕捉。否则编译失败。

  • 如果一个函数调用到了声明了异常的函数,那么该函数要么使用"throws"进行声明要么使用"try-catch"进行捕捉,否则编译失败。

那么我们什么时候使用"throws"进行声明,什么时候使用"try-catch"进行捕捉呢?如果发生异常的内容我们可以进行针对性处理,那么使用"try-catch"进行捕捉;如果我们无法进行处理,那么就用使用"throws"进行声明,由调用者进行处理。

finally?

finally块也是异常处理的一部分。无论是否捕获或处理异常,finally块里的语句都会被执行,它通常用于关闭或释放资源:


class Demo {
    void show()throws Exception
    {
        try
        {
            //开启资源。
            throw new Exception();
        }
        catch(Exception e)
        {
        }
        finally
        {
            //关闭资源。
        }
    }
}

关于finally块,我们需要注意下面几个问题:

  • 当发生以下四种情况时,出现在Java程序中的finally块不一定会被执行。

当程序在进入try语句块之前就出现异常时,比如:


public class Test {

    public static void testFinally() {
        int i = 1 / 0;

        try {
        } catch (Exception e) {
        } finally {
            System.out.println("execute finally");
        }
    }

    public static void main(String[] args) {
        testFinally();
    }
}

当程序在try块中强制退出时,比如:


public class Test {

    public static void testFinally() {
        try { 
            System.exit(0);   // 调用System.exit(0)强制退出
        } catch (Exception e) {
        } finally {
            System.out.println("finally block");
        }
    }

    public static void main(String[] args) {
        testFinally();
    }
}

当程序所在的线程死亡或关闭CPU时,finally块也不会执行。

  • 当try块或catch块中有return语句时,finally块中的代码会执行并且会在return之前执行。因为程序执行return就意味着结束对当前函数的调用并跳出该函数体,因此任何语句要执行都只能在return之前执行。

class Demo {
    public static void main(String[] args) {
        try {
            return;
        }
        catch (Exception e) {
            return;
        }
        finally {
            System.out.println("Finally block");
        }
    }
}

  • 此外,如果try-finally或者catch-finally中都有return语句,那么finally块中的return语句将会覆盖别处的return语句:

class Demo {

    public static int testFinally() {
        try {
            return 1;
        } catch (Exception e) {
            return 0;
        } finally {
            return 3;
        }
    }

    public static void main(String[] args) {
        int result = testFinally();
        System.out.println(result);
    }

}

有关异常的注意事项

子类在覆盖父类方法时,如果父类的方法抛出了异常,那么子类的方法只能抛出父类的异常或者该异常的子类,这也就是说:如果父类的方法没有抛出异常,那么子类覆盖方法时也不能抛出异常,这时就只能使用try-catch进行捕捉;如果父类抛出多个异常,那么子类只能抛出父类异常的子集。具体的原因如下:


// 自定义三个异常
class AException extends Exception 
{
}
class BException extends AException 
{
}
class CException extends Exception 
{
}

// 父类
class Fu
{
    void show() throws AException 
    {
    }
}
// 子类
class Zi extends Fu
{
    void show() throws AException    // 该方法就只能抛出AException或者BException 
    {
    }
}


// 原因如下:

class Test
{
    void method(Fu f)   // Fu f = new Zi();为了让子类也能正常调用该方法,就意味着子类只能抛出该方法能处理的异常。
    {
        try
        {
            f.show();
        }
        catch (AException a)    // 只能处理AException和其子类
        {
        }
    }

    public static void main(String[] args) 
    {
        Test t = new Test();
        t.show(new Zi());
    }
}


原文地址:https://www.cnblogs.com/syhyfh/p/12505198.html