• Java基础:异常学习笔记


    异常

    异常概念

    异常指的是程序执行过程 出现的非正常情况,最终会导致程序非正常停止。

    在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建对象并抛出了一个异常对象。Java处理异常的方式是中断处理。例如程序在执行的过程中出现了空指针,系统就会创建一个空指针的异常对象并交给JVM,JVM会中断程序并在控制台打印出异常信息。

    异常体系

    异常机制其实是帮助我们找到程序中的问题的机制,异常的根类是java.lang.Throwable,其下有两个子类:

    java.lang.Errorjava.lang.Exception其中的java.lang.Exception就是异常类。

    异常体系

    • java.lang.Error:错误,错误无法通过处理解决,只能事先避免,例如创建数组时设置的长度超出系统允许的长度。

    • java.lang.Exception:异常,异常可以通过异常处理代码纠正,使程序继续运行。

    异常分类

    编译时异常(checked异常):在编译时期就能检查出来的异常,如果没有处理异常,则编译失败。如日期格式化异常。

    编译时异常 说明
    ClassNotFoundException 找不到类异常
    IOException I/O异常的根类
    FileNotFoundException 找不到文件异常
    EOFException 文件或流结束异常
    IllegalAccessException 没有类的访问权限异常
    NoSuchMethodException 请求的方法不存在或无访问权限异常
    InterruptedException 线程中断异常

    运行时异常(runtime异常):能通过编译,在运行时期才能检查出来的异常,如数学异常。

    运行时异常 说明
    NullPointerException 空指针异常
    IndexOutOfBoundsException 索引超出范围
    ArrayIndexOutOfBoundsException 数组索引越界
    NumberFormatException 转换为数值类型异常
    ClassCastException 类型转换异常
    ArithmeticException 算数异常

    自定义异常

    自定义一个异常类,只需要继承 ExceptionRuntimeException 即可。

    public class MyExceptionDemo {
        public static void main(String[] args) {
            throw new MyException("自定义异常");
        }
    
        static class MyException extends RuntimeException {
            public MyException(String message) {
                super(message);
            }
        }
    }
    

    输出:

    Exception in thread "main" io.github.dunwu.javacore.exception.MyExceptionDemo$MyException: 自定义异常
    	at io.github.dunwu.javacore.exception.MyExceptionDemo.main(MyExceptionDemo.java:9)
    

    Java处理异常机制

    在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。

      抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。

      捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。

      对于运行时异常、错误或可查异常,Java技术所要求的异常处理方式有所不同。

      由于运行时异常的不可查性,为了更合理、更容易地实现应用程序,Java规定,运行时异常将由Java运行时系统自动抛出,允许应用程序忽略运行时异常。

      对于方法运行中可能出现的Error,当运行方法不欲捕捉时,Java允许该方法不做任何抛出声明。因为,大多数Error异常属于永远不能被允许发生的状况,也属于合理的应用程序不该捕捉的异常。

      对于所有的可查异常,Java规定:一个方法必须捕捉,或者声明抛出方法之外。也就是说,当一个方法选择不捕捉可查异常时,它必须声明将抛出异常。

      能够捕捉异常的方法,需要提供相符类型的异常处理器。所捕捉的异常,可能是由于自身语句所引发并抛出的异常,也可能是由某个调用的方法或者Java运行时 系统等抛出的异常。也就是说,一个方法所能捕捉的异常,一定是Java代码在某处所抛出的异常。简单地说,异常总是先被抛出,后被捕捉的。

      任何Java代码都可以抛出异常,如:自己编写的代码、来自Java开发环境包中代码,或者Java运行时系统。无论是谁,都可以通过Java的throw语句抛出异常。

      从方法中抛出的任何异常都必须使用throws子句。

      捕捉异常通过try-catch语句或者try-catch-finally语句实现。

      总体来说,Java规定:对于可查异常必须捕捉、或者声明抛出。允许忽略不可查的RuntimeException和Error。

    如果想在程序中明确地抛出异常,需要用到 throwthrows

    如果一个方法没有捕获一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。

    抛出异常

    throw 示例:

    public class ThrowDemo {
        public static void f() {
            try {
                throw new RuntimeException("抛出一个异常");
            } catch (Exception e) {
                System.out.println(e);
            }
        }
    
        public static void main(String[] args) {
            f();
        }
    };
    

    输出:

    java.lang.RuntimeException: 抛出一个异常
    

    也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。

    throws 示例:

    public class ThrowsDemo {
        public static void f1() throws NoSuchMethodException, NoSuchFieldException {
            Field field = Integer.class.getDeclaredField("digits");
            if (field != null) {
                System.out.println("反射获取 digits 方法成功");
            }
            Method method = String.class.getMethod("toString", int.class);
            if (method != null) {
                System.out.println("反射获取 toString 方法成功");
            }
        }
    
        public static void f2() {
            try {
                // 调用 f1 处,如果不用 try catch ,编译时会报错
                f1();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            f2();
        }
    };
    

    输出:

    反射获取 digits 方法成功
    java.lang.NoSuchMethodException: java.lang.String.toString(int)
    	at java.lang.Class.getMethod(Class.java:1786)
    	at io.github.dunwu.javacore.exception.ThrowsDemo.f1(ThrowsDemo.java:12)
    	at io.github.dunwu.javacore.exception.ThrowsDemo.f2(ThrowsDemo.java:21)
    	at io.github.dunwu.javacore.exception.ThrowsDemo.main(ThrowsDemo.java:30)
    

    throw 和 throws 的区别:

    • throws 使用在函数上,throw 使用在函数内。
    • throws 后面跟异常类,可以跟多个,用逗号区别;throw 后面跟的是异常对象。

    捕获异常

    使用 try 和 catch 关键字可以捕获异常。try catch 代码块放在异常可能发生的地方。

    示例:

    try {
        // 可能会发生异常的代码块
    } catch (Exception e1) {
        // 捕获并处理try抛出的异常类型Exception
    } catch (Exception2 e2) {
        // 捕获并处理try抛出的异常类型Exception2
    } finally {
        // 无论是否发生异常,都将执行的代码块
    }
    

    此外,JDK7 以后,catch 多种异常时,也可以像下面这样简化代码:

    try {
        // 可能会发生异常的代码块
    } catch (Exception | Exception2 e) {
        // 捕获并处理try抛出的异常类型
    } finally {
        // 无论是否发生异常,都将执行的代码块
    }
    
    • try : try语句用于检查。将要被检查的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
    • catch : catch语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try后面的catch块就会被检查。
    • finally :finally语句块总是会被执行,无论是否出现异常。try-catch语句后不一定非要finally语句。finally常用于这样的场景:由于finally语句块总是会被执行,所以那些在try代码块中打开的,并且必须回收的物理资源(如数据库连接、网络连接和文件),一般会放在finally语句块中释放资源。
    • trycatchfinally 三个代码块中的局部变量不可共享使用。
    • catch 块尝试捕获异常时,是按照catch块的声明顺序从上往下寻找的,一旦匹配,就不会再向下执行。因此,如果同一个try块下的多个catch异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面。

    示例:

    public class TryCatchFinallyDemo {
        public static void main(String[] args) {
            try {
                // 此处产生了异常
                int temp = 10 / 0;
                System.out.println("两个数字相除的结果:" + temp);
                System.out.println("----------------------------");
            } catch (ArithmeticException e) {
                System.out.println("出现异常了:" + e);
            } finally {
                System.out.println("不管是否出现异常,都执行此代码");
            }
        }
    };
    

    打印结果:

    出现异常了:java.lang.ArithmeticException: / by zero
    不管是否出现异常,都执行此代码
    

    异常链

    异常链是以一个异常对象为参数构造新的异常对象,新的异常对象将包含先前异常的信息。

    通过使用异常链,我们可以提高代码的可理解性、系统的可维护性和友好性。

    我们有两种方式处理异常,一是 throws 抛出交给上级处理,二是 try…catch 做具体处理。try…catch 的 catch 块我们可以不需要做任何处理,仅仅只用 throw 这个关键字将我们封装异常信息主动抛出来。然后在通过关键字 throws 继续抛出该方法异常。它的上层也可以做这样的处理,以此类推就会产生一条由异常构成的异常链。

    示例:

    public class ExceptionChainDemo {
        static class MyException1 extends Exception {
            public MyException1(String message) {
                super(message);
            }
        }
    
        static class MyException2 extends Exception {
            public MyException2(String message, Throwable cause) {
                super(message, cause);
            }
        }
    
        public static void f1() throws MyException1 {
            throw new MyException1("出现 MyException1");
        }
    
        public static void f2() throws MyException2 {
            try {
                f1();
            } catch (MyException1 e) {
                throw new MyException2("出现 MyException2", e);
            }
        }
    
        public static void main(String[] args) throws MyException2 {
            f2();
        }
    }
    

    输出:

    Exception in thread "main" io.github.dunwu.javacore.exception.ExceptionChainDemo$MyException2: 出现 MyException2
    	at io.github.dunwu.javacore.exception.ExceptionChainDemo.f2(ExceptionChainDemo.java:29)
    	at io.github.dunwu.javacore.exception.ExceptionChainDemo.main(ExceptionChainDemo.java:34)
    Caused by: io.github.dunwu.javacore.exception.ExceptionChainDemo$MyException1: 出现 MyException1
    	at io.github.dunwu.javacore.exception.ExceptionChainDemo.f1(ExceptionChainDemo.java:22)
    	at io.github.dunwu.javacore.exception.ExceptionChainDemo.f2(ExceptionChainDemo.java:27)
    	... 1 more
    

    异常注意事项

    finally 覆盖异常

    Java 异常处理中 finally 中的 return 会覆盖 catch 代码块中的 return 语句和 throw 语句,所以 Java 不建议在 finally 中使用 return 语句

    此外 finally 中的 throw 语句也会覆盖 catch 代码块中的 return 语句和 throw 语句。

    示例:

    public class FinallyOverrideExceptionDemo {
        static void f() throws Exception {
            try {
                throw new Exception("A");
            } catch (Exception e) {
                throw new Exception("B");
            } finally {
                throw new Exception("C");
            }
        }
    
        public static void main(String[] args) {
            try {
                f();
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }//输出:C
    

    覆盖抛出异常的方法

    当子类重写父类带有 throws 声明的函数时,其 throws 声明的异常必须在父类异常的可控范围内——用于处理父类的 throws 方法的异常处理器,必须也适用于子类的这个带 throws 方法 。这是为了支持多态。

    示例:

    public class ExceptionOverrideDemo {
        static class Father {
            public void start() throws IOException {
                throw new IOException();
            }
        }
    
        static class Son extends Father {
            @Override
            public void start() throws SQLException {
                throw new SQLException();
            }
        }
    
        public static void main(String[] args) {
            Father obj1 = new Father();
            Father obj2 = new Son();
            try {
                obj1.start();
                obj2.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    上面的示例编译时会报错,因为 Son 类抛出异常的实质是 SQLException,而 IOException 无法处理它。那么这里的 try catch 就不能处理 Son 中的异常了。多态就不能实现了。

    异常和线程

    如果 Java 程序只有一个线程,那么没有被任何代码处理的异常会导致程序终止。如果 Java 程序是多线程的,那么没有被任何代码处理的异常仅仅会导致异常所在的线程结束。

    注意事项

    • 对可恢复的情况使用检查性异常(Exception),对编程错误使用运行时异常(RuntimeException)
    • 优先使用 Java 标准的异常
    • 抛出与抽象相对应的异常
    • 在细节消息中包含能捕获失败的信息
    • 尽可能减少 try 代码块的大小
    • 尽量缩小异常范围。例如,如果明知尝试捕获的是一个 ArithmeticException,就应该 catch ArithmeticException,而不是 catch 范围较大的 RuntimeException,甚至是 Exception
    • 尽量不要在 finally 块抛出异常或者返回值
    • 不要忽略异常,一旦捕获异常,就应该处理,而非丢弃
    • 异常处理效率很低,所以不要用异常进行业务逻辑处理
    • 各类异常必须要有单独的日志记录,将异常分级,分类管理,因为有的时候仅仅想给第三方运维看到逻辑异常,而不是更细节的信息。
    • 如何对异常进行分类
      • 逻辑异常,这类异常用于描述业务无法按照预期的情况处理下去,属于用户制造的意外。
      • 代码错误,这类异常用于描述开发的代码错误,例如 NPE,ILLARG,都属于程序员制造的 BUG。
      • 专有异常,多用于特定业务场景,用于描述指定作业出现意外情况无法预先处理。
  • 相关阅读:
    如何保证 Redis 缓存与数据库双写一致性?
    如何合理地估算线程池大小?
    不用装工具,一条 Linux 命令就能实现文件上传下载!
    看了 Google 大神 Jeff Dean 的传说,我拜服了~
    div设置水平垂直居中
    "起用"与"启用"
    徇私舞弊
    精选排比金句20例
    一笔画图推
    一笔画
  • 原文地址:https://www.cnblogs.com/jinjun99/p/13424108.html
Copyright © 2020-2023  润新知