• 异常处理


    正文

    异常是什么?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());
        }
    }
    
    
    
  • 相关阅读:
    mojoPortal学习笔记之页面访问权限控制
    页面中添加某模块
    styletreeview.css页面菜单
    mojoPortalprovider模式学习之1.1之IndexBuilderManager
    mojoportal学习笔记之HtmlContent模块
    CLR via c#学习笔记 之 引用类型与值类型
    mojoportal中解决下载文件名乱码问题
    mojoportal学习笔记之显示所有菜单
    blogmodule.css博客栏目
    [转]数据访问组件SqlHelper
  • 原文地址:https://www.cnblogs.com/syhyfh/p/12505198.html
Copyright © 2020-2023  润新知