Java零基础入门笔记15-Java异常

1、异常简介

  • 所谓异常,从字面上来看就是意外例外的意思。即在程序运行过程中,意外发生的情况,背离我们程序本身的意图的表现,都可以理解为异常。
  • Java提供了强大的异常处理机制,通过合理的异常处理,可以提高程序的健壮性。
  • 在Java当中是通过Throwable及其相关子类来对各种异常进行描述的。Throwable是Java异常的根类,它有两个重要的子类ErrorException
    • Error:是程序无法处理的错误,表示运行应用程序中较严重的问题。常见的错误有虚拟机错误内存溢出线程死锁等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。对于发生的错误,我们也无法通过异常处理来去解决它所引起的各种状况。所以针对Error及其子类所产生的异常,我们通常是不需要关心的。
    • Exception:是程序本身可以处理的异常。通常我们提到的异常处理就是针对Exception及其子类的处理。Exception包括非检查异常(UncheckedException)和检查异常(CheckedException)。
      • 非检查异常:编译器不要求强制处理的异常,它包括RuntimeException及其相关子类。像空指针异常数组下标越界算数异常类型转换异常等等。即在编写代码时,可以选择捕获这些异常,也可以放任不管,而编译器是不会针对这些异常给出任何错误提示信息的。
      • 检查异常:编译器要求必须处理的异常。
        这里写图片描述
  • 下面我们主要是针对Exception及其子类进行相关的异常处理。

2、异常处理简介

  • 在Java程序中,异常的处理机制通常分为:抛出异常捕获异常。当然啦,异常只有先被抛出然后才能被捕获(嘿嘿~~)。
    • 抛出异常:比如当一个方法中出现错误引发异常时,该方法会去创建异常对象,并且交付给运行时系统进行处理。在这个异常对象当中通常会包含:异常类型、异常出现时的程序状态等信息
    • 捕获异常:当运行时系统捕获到异常,此时,运行时系统就会去寻找合适的处理器,如果找到了与抛出的异常匹配的处理器,那么就会执行相关的处理逻辑;如果始终没有找到,那么运行时系统就会中止,也就意味着Java程序停止运行。
  • 对于检查异常(CheckedException)必须捕获或者声明抛出
  • 对于非检查异常(Runtime及其子类)和Error则允许忽略
  • 针对抛出异常和捕获异常,在Java中是通过5个关键字来实现的,即try、catch、finally、throwthrows
    • 捕获异常:
      • try:执行可能产生异常的代码。
      • catch:捕获异常。
      • finally:无论是否发生异常,代码总是执行。
    • 抛出异常:
      • throw:手动抛出异常。
      • throws:声明可能要抛出的异常。

3、使用try…catch…finally实现异常处理

public void method(){
    try {
        // 代码段1
        // 产生异常的代码段2
    } catch (Exception e) {
        // 对异常进行处理的代码段3
    } finally {
        // 代码段4
    }
}
  • try块:用于捕获异常。
  • catch块:用于针对try块当中捕获到的异常进行处理。
  • finally块:无论是否发生异常代码总会执行。
  • try块后可以零个或多个catch块,如果没有catch块,则必须跟一个finally块。简单来说,就是try必须和catch或者finally组合使用。

3.1、try…catch块

  • 1.下面进行代码演示,首先创建一个名为ExceptionProj的Java项目,并新建一个名为com.cxs.test测试包,在包下新建一个名为TryCatchDemo的类,并选中main方法。
public class TryCatchDemo {
    public static void main(String[] args) {
        Scanner keyboardInput = new Scanner(System.in);
        System.out.print("请输入第一个整数:");
        int one = keyboardInput.nextInt();
        System.out.print("请输入第一个整数:");
        int two = keyboardInput.nextInt();
        System.out.println("one/two=" + one / two);
        keyboardInput.close();
    }
}
  • 2.运行代码,按照下图进行尝试。
    这里写图片描述
  • 3.下面我们来使用try...catch块去捕获异常。按下图进行快速操作,并进行代码修改。
    这里写图片描述
public class TryCatchDemo {
    public static void main(String[] args) {
        System.out.println("运算开始!");
        Scanner keyboardInput = new Scanner(System.in);
        try {
            System.out.print("请输入第一个整数:");
            int one = keyboardInput.nextInt();
            System.out.print("请输入第一个整数:");
            int two = keyboardInput.nextInt();
            System.out.println("one/two=" + one / two);
        } catch (ArithmeticException e) {
            System.out.println("您输入的除数为0!");
            e.printStackTrace();
        } catch (InputMismatchException e) {
            System.out.println("您输入的为非数字字符!请输入整数");
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        keyboardInput.close();
        System.out.println("运算结束!");
    }
}

注意:

  • try后面跟着多个catch块时,不能出现同类型异常
  • 父类Exception异常应该放到最后来进行捕获,这样也可以捕获到前面的catch块无法捕获的异常信息,而最终捕获到异常,都会被父类Exception收入囊中(嘿嘿~~),这也算是一种安全保障机制。
  • catch和finally是不能脱离try而单独存在的(这里没有添加finally,大家可以自行尝试)。
  • 4.运行代码,分别进行尝试。与不捕获异常相比,捕获异常后,我们可以继续向下执行代码(运算结束被打印出来,代表程序完整地运行了下来)。
    这里写图片描述

3.2、中止finally的执行

通过前面所讲我们知道,通常情况下,在finally中的代码段,无论是否发生异常代码总会执行。那么什么情况下,会中止finally的执行呢?

  • 5.修改一下代码,如下所示,将打印运算结束的语句放在finally中,并在捕获算数异常的catch块当中添加一句代码,如下所示。
public class TryCatchDemo {
    public static void main(String[] args) {
        System.out.println("运算开始!");
        Scanner keyboardInput = new Scanner(System.in);
        try {
            System.out.print("请输入第一个整数:");
            int one = keyboardInput.nextInt();
            System.out.print("请输入第一个整数:");
            int two = keyboardInput.nextInt();
            System.out.println("one/two=" + one / two);
        } catch (ArithmeticException e) {
            System.exit(1);//←添加改行代码
            System.out.println("您输入的除数为0!");
            e.printStackTrace();
        } catch (InputMismatchException e) {
            System.out.println("您输入的为非数字字符!请输入整数");
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            System.out.println("运算结束!");
        }
        keyboardInput.close();
    }
}
  • 6.运行代码,这里我们就做一个捕获算数异常的尝试,结果如下所示,我们再输入0后,finally快中的输出语句并没有执行,并且控制台的停止按钮并不是红色,而显示灰色,对,此时程序已经停止了运行。这便是System.exit(1);所起的作用。
    这里写图片描述
  • 7.我们去官方API查看一下System的该方法介绍,如下图,作用为:终止当前正在运行的Java虚拟机
    这里写图片描述
  • 8.点进去看看exit中的参数含义,如下图:参数用作状态代码,按照惯例,非零状态代码表示异常终止
    这里写图片描述

3.3、return关键字在异常处理中的作用

  • 通过前面的学习我们知道,return可以中止方法的执行,并将返回值带回调用处
  • 9.新建一个测试类Test,下面继续使用上面的代码,并做一定的修改。
public class Test {
    public static int test() {
        System.out.println("运算开始!");
        Scanner keyboardInput = new Scanner(System.in);
        try {
            System.out.print("请输入第一个整数:");
            int one = keyboardInput.nextInt();
            System.out.print("请输入第一个整数:");
            int two = keyboardInput.nextInt();
            return one / two;
        } catch (ArithmeticException e) {
            System.out.println("您输入的除数为0!");
            return 0;
        } finally {
            System.out.println("运算结束!");
            keyboardInput.close();
            return 10000;
        }

    }

    public static void main(String[] args) {
        int result = test();
        System.out.println("one和two的商为:" + result);
    }
}
  • 10.运行代码,并做相应的尝试,结果如下。发现,无论是正常执行,还是捕获算数异常,最终返回的都为finally中的值(由finally的强制执行造成的结果,这里可以尝试打断点调试,查看程序执行的顺序)。
    这里写图片描述
  • 11.其实我们在finally中编写return语句时,编译器就给出相应的黄色波浪线警告了,如下图所示。
    这里写图片描述
  • 12.此时将finally块中return语句注释,则警告消失,再次运行代码,便会得到符合预期的结果(结果略)。

4、使用throw和throws实现异常处理

可以通过throws声明将要抛出何种类型的异常,通过throw将产生的异常抛出。

4.1、使用throws声明异常类型

  • 如果一个方法可能会出现异常,但没有能力处理这种异常。可以在方法声明处用throws子句来声明抛出异常。如下图所示,throws后面跟的是异常类型,并不是异常对象。
public void method() throws Exception1,Exception2,...,ExceptionN{
    //可能产生异常的代码
}
  • 当方法抛出异常列表中的异常时,方法将不对这些类型及其子类类型的异常做处理,而抛向调用该方法的方法,由调用方法去处理异常

  • 1.在test包下新建一个名为ThrowsDemo类,并勾选main方法,将上面的代码复制一份,修改代码如下。
public class ThrowsDemo {
    public static int test() throws ArithmeticException, InputMismatchException {
        System.out.println("运算开始!");
        Scanner keyboardInput = new Scanner(System.in);
        System.out.print("请输入第一个整数:");
        int one = keyboardInput.nextInt();
        System.out.print("请输入第一个整数:");
        int two = keyboardInput.nextInt();
        System.out.println("运算结束!");
        keyboardInput.close();
        return one / two;
    }

    public static void main(String[] args) {
        test();
    }
}
  • 2.在上面的代码中,我们再不捕获异常的情况下,编译器并没有报错,原因是算数异常和类型不匹配异常均属于非检查异常,而编译器针对非检查异常可以忽略,因而没有提示错误。
  • 3.若在抛出的异常类型中加上Exception,此时在test()调用方法处会报错(因为Exception包含检查异常和非检查异常,而检查异常既然已经声明抛出了,则要求必须对异常进行捕获并处理的)。
    这里写图片描述
  • 4.下面我们将代码补充完整,利用快捷键Alt+Shift+Z快速完成代码。
public class ThrowsDemo {
    public static int test() throws ArithmeticException, InputMismatchException {
        System.out.println("运算开始!");
        Scanner keyboardInput = new Scanner(System.in);
        System.out.print("请输入第一个整数:");
        int one = keyboardInput.nextInt();
        System.out.print("请输入第一个整数:");
        int two = keyboardInput.nextInt();
        System.out.println("运算结束!");
        keyboardInput.close();
        return one / two;
    }

    public static void main(String[] args) {
        try {
            int result = test();
            System.out.println("one和two的商为:" + result);
        } catch (InputMismatchException e) {
            System.out.println("您输入的为非数字字符,请输入整数!");
            e.printStackTrace();
        } catch (ArithmeticException e) {
            System.out.println("您输入的除数为0!");
            e.printStackTrace();
        } catch (Exception e) {// 为保险起见,此处加上捕获Exception
            System.out.println("程序出错了!");
            e.printStackTrace();
        }
    }
}

4.2、使用throw抛出异常对象

  • throw用来抛出异常。例如:throw new IOException();
  • throw抛出的只能是可抛出类Throwable或其子类的实例对象
  • 当方法中包含throw抛出对象的语句时,通常有两种处理方式:
    • ①在throw语句外套上try...catch块,即自己抛出自己处理;
    • ②在抛出异常的方法声明处通过throws关键字标识对应的异常类型,即谁调用谁处理。
public method (){//方式1
    try{
        //代码段1
        throw new 异常类型();
    } catch(异常类型 e){
        //对异常进行处理的代码段2
    }
}

public void method() throws 异常类型{//方式2
    //代码段1
    throw new 异常类型();
}

  • 1.下面进行代码实践。在test包下新建一个名为ThrowDemo的类,并勾选上主方法。
  • 2.定义一个testAge方法,如下所示,在利用throw关键字抛出异常时,编译器会给出以下提示,正好对应上面所讲的两种方式。
    这里写图片描述

  • 3.根据上面的代码编辑器给出的提示,我们先采用方式1,如下图所示,声明Throwable或Exception均可。
    这里写图片描述

    这里将throw new Exception()提取为warning静态方法,否则编译器会给出黄色波浪线警告
    这里写图片描述

public class ThrowDemo {
    public static void main(String[] args) {
        try {// 在调用处进行异常处理,当然此时还可以继续向上抛出,交给虚拟机处理
            testAge();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void testAge() throws Exception {
        System.out.println("请输入您的年龄:");
        Scanner input = new Scanner(System.in);
        int age = input.nextInt();
        if (age < 18 || age > 80) {
            warning();
        } else {
            System.out.println("欢迎入住本酒店!");
        }
        input.close();
    }

    private static void warning() throws Exception {
        throw new Exception("18岁以下,80岁以上的住客必须由亲友的陪同才能入住!");
    }
}
  • 4.运行程序,输入82,结果如下。
    这里写图片描述
  • 5.再来看一下方式2。
public class ThrowDemo {
    public static void main(String[] args) {
        testAge();
    }

    public static void testAge() {
        try {
            System.out.println("请输入您的年龄:");
            Scanner input = new Scanner(System.in);
            int age = input.nextInt();
            if (age < 18 || age > 80) {
                warning();
            } else {
                System.out.println("欢迎入住本酒店!");
            }
            input.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void warning() throws Exception {
        throw new Exception("18岁以下,80岁以上的住客必须由亲友的陪同才能入住!");
    }
}
  • 6.运行代码,输入年龄2,结果如下。
    这里写图片描述

5、自定义异常

  • 使用Java内置的异常类可以描述在编程时出现的大部分异常情况。
  • 当然,也可以通过自定义异常描述特定业务产生的异常类型。
  • 所谓的自定义异常,就是定义一个类,去继承Throwable类或它的子类。

  • 1.在test包下新建一个名为HotelAgeException,并继承自Exception类。
public class HotelAgeException extends Exception {
    public HotelAgeException() {
        super("18岁以下,80岁以上的住客必须由亲友的陪同才能入住!");
    }
}
  • 2.修改ThrowDemo的代码。
public class ThrowDemo {
    public static void main(String[] args) {
        try {// 在调用处进行异常处理,当然此时还可以继续向上抛出,交给虚拟机处理
            testAge();
        } catch (HotelAgeException e) {
            System.out.println(e.getMessage());
            System.out.println("酒店前台工作人员不允许办理入住登记!");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("除自定义异常之外,其余的异常在这里进行处理!");
        }
    }

    public static void testAge() throws HotelAgeException {
        System.out.println("请输入您的年龄:");
        Scanner input = new Scanner(System.in);
        int age = input.nextInt();
        if (age < 18 || age > 80) {
            throw new HotelAgeException();
        } else {
            System.out.println("欢迎入住本酒店!");
        }
        input.close();
    }
}
  • 3.运行代码,做三次尝试,结果如下所示。
    这里写图片描述

6、异常链

异常链是一种面向对象编程技术,指将捕获的异常包装进一个新的异常中并重新抛出的异常处理方式。原异常被保存为新异常的一个属性(比如cause)。【引自百科】

  • 1.创建一个名为ExceptionChain类,并勾选主方法。在其中定义三个静态方法,用于异常的连续抛出,在主方法中对抛出的异常进行处理。
public class ExceptionChain {
    public static void main(String[] args) {
        try {
            testThree();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void testOne() throws HotelAgeException {
        throw new HotelAgeException();
    }

    public static void testTwo() throws Exception {
        try {
            testOne();
        } catch (HotelAgeException e) {
            throw new Exception("我是新产生的异常1");
        }
    }

    public static void testThree() throws Exception {
        try {
            testTwo();
        } catch (Exception e) {
            throw new Exception("我是新产生的异常2");
        }
    }
}
  • 2.运行代码,发现只获取到了最后一个方法的异常信息,即丢失了前两个方法的异常信息。
    这里写图片描述
针对异常信息的丢失该如何解决呢?有办法将前面的异常信息保留下来吗?不要担心,在Java的API中提供了保留异常的机制,我们来看一下Throwable的帮助文档。
  • 构造器Throwable(String message, Throwable cause):用指定的详细信息和原因构造一个新的可投掷的
  • 方法initCause(Throwable cause):用一个异常信息初始化一个新的异常。

  • 3.采用上面的构造器的方法,在testTwotestThree方法中分别采用构造器Throwable(String message, Throwable cause)和方法initCause(Throwable cause)将原异常信息添加到新的异常当中并抛出。
public static void testTwo() throws Exception {
    try {
        testOne();
    } catch (HotelAgeException e) {
        throw new Exception("我是新产生的异常1", e);// ←——将捕获的异常作为构造器的第二个参数加进来
    }
}

public static void testThree() throws Exception {
    try {
        testTwo();
    } catch (Exception e) {
        // ←——采用initCause方法将原异常信息添加到新的异常对象中
        Exception e2 = new Exception("我是新产生的异常2");
        e2.initCause(e);
        throw e2;
    }
}
  • 4.运行代码,结果如下所示。
    这里写图片描述

总结:随着异常从底层逐层抛出传给上层,然后将这些异常发生的原因串连起来,这便是异常链。

猜你喜欢

转载自blog.csdn.net/chaixingsi/article/details/82228506