• Java基础(四)-异常处理机制及其设计


      本篇主要是记录自己所理解的Java异常处理机制(基于jdk1.7)以及怎么去处理和设计异常。还记得当初学习Java异常这块的时候都没怎么注意它的用途,以为就是简单的处理下异常,我避免程序出现这样错误就行了(当初真的很懵懂很天真)。当踏入社会工作后才知道一个好的异常处理框架是有多重要,比方说当你的项目投入使用的时候,当程序出错如何快速的定位到问题的根源(出了什么错,在哪出的错,为什么出错),这就跟你的异常处理的好坏有关了(当然离不开你的日志处理)。在有效使用异常的情况下,异常类型会回答报的什么错,异常堆栈跟踪回答在哪出的错,异常信息会回答为什么出错。所以如果设计和处理的不好,将会花费大量时间去维护(出现这样的情况公司肯定是不愿意的同时也会质疑你的能力了)。

      本篇将从下面几个方面进行记录,如果发现有错误的地方还请各位大佬们指出以便改进,谢谢。

    一、Java异常结构

      首先你得明白异常是什么,为什么发生异常。通俗的讲,程序异常就是程序出现了错误,而这种错误可能是逻辑上的错误也可能是系统上的错误。在Java中把异常当做对象处理,所以我们要了解在java中有哪些可以类是用来描述异常的,它的层次结构图如下(只描述写重要的类):

    二、异常分类及其常见异常类

      在java中,所有的异常都是通过Throwable类及其子类传播的(从上面层次结构图中可以看出)。根据层次关系我们依次来分析:

    • Throwable:在它下面有两个重要的子类:Error和Exception。它们两个都是重要的异常处理类,但两者分别承担的角色或者针对的对象却不一样,请看下面分析。
    • Error:是指程序无法处理的错误,也称unchecked exceptions(未经检查的异常:编译器不要求强制处置的异常)。比如:Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
    • Exception:是程序本身可以处理的异常。其中除了RuntimeException及其子类外的任何Exception子类都称为checked exceptions(受检查的异常:编译器要求必须处置的异常)。自然RuntimeException及其子类就归属于非受检查的异常。
    /**
     * The class {@code Exception} and its subclasses are a form of
     * {@code Throwable} that indicates conditions that a reasonable
     * application might want to catch.
     *
     * <p>The class {@code Exception} and any subclasses that are not also
     * subclasses of {@link RuntimeException} are <em>checked
     * exceptions</em>.  Checked exceptions need to be declared in a
     * method or constructor's {@code throws} clause if they can be thrown
     * by the execution of the method or constructor and propagate outside
     * the method or constructor boundary.
     *
     * @author  Frank Yellin
     * @see     java.lang.Error
     * @jls 11.2 Compile-Time Checking of Exceptions
     * @since   JDK1.0
     */
    public class Exception extends Throwable {

      根据上面Excepiton类还可以分成这两大类:运行时异常和非运行时异常。

    • 运行时异常:都是RuntimeException类及其子类。例如常见类:NullPointerException(空指针)、IndexOutOfBoundsException(下标越界异常)、ArithmeticException(算术异常)、ClassNotFoundException(找不到类异常)、IllegalArgumentException (非法参数异常)等。这些异常也属于非受检查异常。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
    • 非运行时异常:是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。例如常见类:IOException、SQLException、ParseException、FileNotFoundException(文件找不到异常)等。
    • 综上分类,可以将Throwable划分成这样:

    三、异常处理机制

       在java中异常处理机制大致为:先抛出异常再捕捉异常。抛出异常一般是交给java虚拟机,当然也可以自己手动抛出异常(下面会涉及到),重点是怎么捕捉异常。

      1、捕获异常(try、catch、finally)

      捕获异常的形式可能是上面三个的组合,组合形式有:try..catch、try...finally和try..catch..finally

      主要记录下try..catch..finally的主要事项:

      1.1、其中catch块可以有多个,但要注意捕获异常类的顺序:子类一定要在父类前,否则会编译不通过。

        @Test
        public void test1() {
            int[] arr = {1,3,5,0};
            try{
                for (int i = 0; i < arr.length; i ++) {
                    System.out.println(10/arr[i]);
                }
            }catch (Exception e){
                e.printStackTrace();
            }catch (ArithmeticException e){//编译不通过。因为异常在前面的catch块中捕获。
                e.printStackTrace();
            }finally{
                System.out.println("end");
            }
        }

      1.2  当catch块中有return时要注意其执行顺序:finally语句在return返回之前执行。

    public class ExceptionTest {
        
        @Test
        public void test1() {
            System.out.println("计算结果:" + sum());
        }
        
        public int sum() {
            int count = 0;
            int[] arr = {1,3,5,0};
            try{
                for (int i = 0; i < arr.length; i ++) {
                    count += 10/arr[i];
                }
            }catch (ArithmeticException e){
                System.out.println("计算失败,分母不能为0:" + e.getMessage());
                return count;
            }finally{
                System.out.println("sum() end");//会先执行
            }
            return count;
        }
    }

      ouput:

    计算失败,分母不能为0:/ by zero
    sum() end
    计算结果:15

      1.3  当catch块中有return时要注意fianlly语句对其返回值进行修改时是否影响其返回值:基本类型及其字符串类型是不会被改变的,对象类型下成员变量的值是可以改变的。

     1 public class ExceptionTest {
     2     
     3     @Test
     4     public void test1() {
     5         System.out.println("基本类型mod1()=" + mol1());
     6         System.out.println("字符串类型mod2()=" + mol2());
     7         System.out.println("对象类型mod3()=" + mol3());
     8     }
     9     
    10     public int mol1() {
    11         int count = 0;
    12         int[] arr = {1,3,5,0};
    13         try{
    14             for (int i = 0; i < arr.length; i ++) {
    15                 arr[i] = 10/arr[i];
    16             }
    17         }catch (ArithmeticException e){
    18             return count;
    19         }finally{
    20             //基本类型修改无效,其实这相当于一个局部变量
    21             count = -1;
    22         }
    23         return count;
    24     }
    25     
    26     public String mol2() {
    27         String msg = "success";
    28         int[] arr = {1,3,5,0};
    29         try{
    30             for (int i = 0; i < arr.length; i ++) {
    31                 arr[i] = 10/arr[i];
    32             }
    33         }catch (ArithmeticException e){
    34             msg = "fail";
    35             return msg;
    36         }finally{
    37             //字符串修改无效,其实这相当于一个局部变量
    38             msg = "fail, finally";
    39         }
    40         return msg;
    41     }
    42     public StringBuffer mol3() {
    43         StringBuffer msg = new StringBuffer();
    44         int[] arr = {1,3,5,0};
    45         try{
    46             for (int i = 0; i < arr.length; i ++) {
    47                 arr[i] = 10/arr[i];
    48             }
    49         }catch (ArithmeticException e){
    50             msg.append("fail");
    51             return msg;
    52         }finally{
    53             //对象类型,其成员变量char[] value;的值是可以被改变的
    54             msg.append(", finally");
    55         }
    56         return msg;
    57     }
    58     
    59 }

      output:

    基本类型mod1()=0
    字符串类型mod2()=fail
    对象类型mod3()=fail, finally

      1.4  当finally语句中有返回值时,会覆盖catch中的返回语句,也会使catch块的throw 抛出的异常失效。(其实不建议使用的)

    public class ExceptionTest {
        
        @Test
        public void test1() {
            System.out.println("基本类型mod1()=" + mol4());
            System.out.println("字符串类型mod2()=" + mol5());
        }
        
        public int mol4() {
            int count = 0;
            int[] arr = {1,3,5,0};
            try{
                for (int i = 0; i < arr.length; i ++) {
                    arr[i] = 10/arr[i];
                }
            }catch (ArithmeticException e){
                return count;
            }finally{
                count = -1;
                return count;//直接返回值,不会在进入catch块中执行
            }
        }
        
        public String mol5() {
            String msg = "success";
            int[] arr = {1,3,5,0};
            try{
                for (int i = 0; i < arr.length; i ++) {
                    arr[i] = 10/arr[i];
                }
            }catch (ArithmeticException e){
                throw new ArithmeticException("分母不能为0");
            }finally{
                //字符串修改无效,其实这相当于一个局部变量
                msg = "fail, finally";
                return msg;//直接返回值,不会在进入catch块抛出异常
            }
            //return msg;
        }
    }

      output:

    基本类型mod1()=-1
    字符串类型mod2()=fail, finally

      2、抛出异常,有两种方式:使用throws指定异常,在方法上使用;可以使用throw new 的方法,在方法里使用。不要搞混淆就行。

    四、有关设计和处理异常的几个建议(参考Effective Java)

      1、只针对异常的情况才使用异常,不要用异常去控制正常流程。(这点我想大部分人不会这么做,但是也列举出来,如果这么做了会引起莫名奇妙的问题)

      2、避免不必要的使用受检异常。

      3、优先使用标准异常,即错误信息相对应的异常,不要乱抛出异常类否则会找不到问题的根源。比方说有个地方是数组索引超限,你却抛出个参数非法异常,这样就掩盖了原始异常信息。

      4、不要忽略异常,即不要使用空catch块。(没有异常信息,就无法找出问题的所在)

      5、尽量捕获写细节信息。比方说数组索引越界,它是超过最大索引还是小于0?

  • 相关阅读:
    ES6 -- (1) 简介、let、块级作用域、const、顶层对象的属性、globalThis对象
    TS -- (5)声明合并、代码检查
    TS -- (4)类、类与接口、泛型
    TS -- (3)声明文件、内置对象、类型别名、字符串字面量类型、元组、枚举
    TS -- (2)接口、数组的类型、函数的类型
    TS -- (1)环境,快速入门,基础类型,类型断言、变量声明、解构、展开
    css的三定位方式的区别
    Array循环
    scroll操作
    【Nodejs】记一次图像识别的冒险
  • 原文地址:https://www.cnblogs.com/yuanfy008/p/8110897.html
Copyright © 2020-2023  润新知