Day15:异常

任务 使用异常处理机制解决问题

关键步骤如下:
➢使用try-catch 块处理异常。
➢使用try-catch-finally块处理异常。
➢使用多重catch块处理异常。
➢自定义异常。

异常概述

1.认识异常

异常是指在程序的运行过程中所发生的不正常事件,如所需文件找不到、网络连接不通或连接中断、算术运算出错(如被零除)、数组下标越界、装载一个不存 在的类、对null对象操作、类型转换异常等。异常会中断正在运行的程序。
下面通过示例1认识程序中的异常。
示例1
编写程序实现根据提示输入被除数和除数,计算并输出商,最后输出“感谢使用本程序!”信息。
关键代码:

public class Test {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        System.out.print("请输入被除数:");
        int num1 = in.nextInt();
        System.out.print("请输入除数:");
        int num2 = in.nextInt();
        System.out.println(String.format("%d/%d=%d", num1, num2, num1 / num2));
        System.out.println("感谢使用本程序!");
    }
}

代码分析如下。
在示例1的代码中,“String.format(”%d/%d=%d", num1, num2, num1 / num2);"使
用了指定的格式字符串和参数返回一个格各式化字符串,此处将num1, num2, 和numl/mum2都以整型的形式输出。

正常情况下,用户会按照系统的提示输入整数,除数不能为0。

但是,如果用户没有按要求进行输入,如被除数输入了“B",则程序运行时将会发生异常

若除数输入了“0”,则程序运行时也将发生异常。

一旦 出现异常程序将会立刻结束,不仅计算和输出商的语句不会执行,就连输出“感谢使用本程序!”的语句也不会执行。可以通过增加if-else语句对各种异常情况进行判断处理,代码如示例2所示。
示例2
使用if-else语句处理示例1中的异常。
关键代码:

public class Test2 {
    public static void min(String[] args {
        Scanner in = new Scanner(System.in);
        System.out.println("请输入被除数:");
        int num1 = 0;
        if (in.hasNextInt()) {//如果输入的被除数是整数
            num1 = in.nextInt();//如果输入的被除数不是整数
        } else {
            System.err.println("输入的被除数不是整数,程序退出。 ");
            System.exit(1);//结束程序
        }
        System.out.print("请输入除数:");
        int num2 = 0;
        if (in.hasNextInt()) {//如果输入的除数是整数
            num2 = in.nextInt();
            if (0 == num2) {//如果输入的除数是0
                System.err.println(" 输入的除数是0,程序退出。 ");
                System.exit(1);
            } else { //如果输入的除数不是整数
                System.err.println(" 输入的除数不是整数,程序退出。 ");
                System.exit(1);
                System.out.println(String.format("%d / %d=%d", num1, num2, num1 / num2));
                System.out.println("感谢使用本程序! ");
            }
        }
    }
}

通过if-else语句进行异常处理,有以下缺点。
➢代码臃肿, 加入了大量的异常情况判断和处理代码。
➢程序员把相当多的精力放在了异常处理代码上,放在了“堵漏洞”上,占用了编写业务代码的时间,必然影响开发效率。
➢很难穷举所有的异常情况,程序仍旧不健壮。
➢异常处理代码和业务代码交织在一起, 影响代码的可读性,加大日后程序的维护难度。

Java提供了异常处理机制,可以由系统来处理程序在运行过程中可能出现的异常事件,使程序员有更多精力关注于业务代码的编写。

2.Java异常体系结构

Java中的异常有很多类型,异常在Java中被封装成了各种异常类,Java 的异常体系结构如图所示。
在这里插入图片描述

所有异常类型都是Throwable类的子类,它派生了两个子类: Error 类和Exception类。
(1)Error类:表示仅靠程序本身无法恢复的严重错误,如内存溢出、动态链接失败、虚拟机错误。应用程序不应该抛出这种类型的错误(一般由虚拟机抛出)。假如出现这种错误,应尽力使程序安全退出。所以在进行程序设计时,应该更关注Exception类。

(2) Exception 类:由Java应用程序抛出和处理的非严重错误,如所需文件找不到、网络连接不通或连接中断、算术运算出错(如被零除)、数组下标越界、装载一个不存在的类、对null对象操作、类型转换异常等。它的各种不同的子类分别对应不同类型的异常。Exception又可分为两大类异常。
➢运行时异常:包括RuntimeException及其所有子类。不要求程序必须对它们进行处理,如示例1中的算术异常ArithmeticException。本章重点讲解的就是这类异常。
➢Checked异常(非运行时异常) :除了运行时异常外的其他从Exception类继承来的异常类。
下表列出了一些常见的异常类及其用途,读者现阶段只需初步了解这些异常类即可。在以后的编程中,可以根据系统报告的异常信息,分析异常类型来判断程序到底出现了什么问题。
在这里插入图片描述

Java异常处理机制

1.异常处理

异常处理机制就像人们对平时可能会遇到的意外情况,预先想好了一些处理的办法。
在程序执行代码时,若发生了异常,程序会按照预定的处理办法对异常进行处理,异常
处理完毕之后,程序继续运行。

Java的异常处理是通过5个关键字来实现的,即try、catch、 finally、 throw 和throws。

2.使用try-catch处理异常

Java中提供了try-catch结构进行异常捕获和处理,把可能出现异常的代码放入try语句块中,并使用catch语句块捕获异常。

示例3
使用try-catch捕获并处理示例1中的异常。
关键代码:

public class Test3 {
    public static void main(String[] args) {
        try {
            Scanner in = new Scanner(System.in);
            System.out.print("请输入被除数:");
            int num1 = in.nextInt();
            System.out.print(" 请输入除数:");
            int num2 = in.nextInt();
            System.out.println(String.format("%d /%d= %d", num1, num2, num1 / num2));
            System.out.println("感谢使用本程序! ");
        } catch (Exception e) {
            System.err.println("出现错误:被除数和除 数必须是整数," + " 除数不能为零。");
            e.printStackTrace();
        }
    }
}

try-catch语句块的执行流程比较简单,首先执行的是try语句块中的语句,这时可能会出现以下3种情况。
(1)如果ty语向块中所有语句正常执行完毕,没有发生异常,那么catch语句块中的所有语句都将会被忽略。当在控制台输入两个整数时,示例3中的ty语句块中的代码将正常执行,不会执行catch语句块中的代码。

(2)如果try语句块在执行过程中发生异常,并且这个异常与catch语句块中声明的异常类型匹配,那么try语句块中剩下的代码都将被忽略,而相应的catch语句块将会被执行。匹配是指catch所处理的异常类型与所生成的异常类型完全一致或是它的父类。当在控制台提示输入被除余数时输入了“B”,示例3中try语句块中的代码:“"intnum1=in.nextInt( );"将抛出InputMismatchException异常。由于InputMismatchException实Exception的子类,程序将忽略try语句块中剩下的代码而去执行catch语句块。

(3)如果try语句块在执行过程中发生异常,而抛出的异常在catch语句块中没有被声明,那么方法立刻退出。

如示例3所示,在catch语句块中可以加入用户自定义处理信息,也可以调用异常对象的方法输出异常信息,常用的方法如下。
➢void printStackTrace():输出异常的堆栈信息。堆栈信息包括程序运行到当前类的执行流程,它将输出从方法调用处到异常抛出处的方法调用序列。
➢String getMessage: 返回异常信息描述字符串,该字符串描述了异常产生的原因,是printStackTrace()输出信息的部分。

注意
如果try语句块在执行过程中发生异常,try语句块中剩下的代码都将被忽略,系统会自动生成相应的异常对象,包括异常的类型、 异常出现时程序的运行状态发对该异常的详细描述,如果这个异常对象与catch语句块中声明的异常类型区配,会把该异常对象賦給catch后面的异常参数,相应的catch语句块将会被执行。

3.使用try-catch- finally处理异常

如果希望示例3中不管是否发生异常,都执行输出“感谢使用本程序!”语句,就需要在try-catch 语句块后加入finally语句块,把要执行输出的语向放入finally语句块中。无论是否发生异常,finally 语句块中的代码总能被执行,如示例4所示。

示例4
使用try-catch-finally捕获并处理示例1中的异常。
关键代码:

import java.util.Scanner;

public class Test4 {
    public static void main(String[] args) {
        try {
            Scanner in = new Scanner(System.in);
            System.out.print("请输入被除数:");
            int num1 = in.nextInt();
            System.out.print("请输入除数:");
            int num2 = in.nextInt();
            System.out.println(String.format("%d / %d=%d", num1, num2, num1 / num2));
        } catch (Exception e) {
            System.err.println("出现错误:被除数和除数必须是整数,” +”除数不能为零。");
            System.out.println(e.getMessage());
        } finally {
            System.out.println("/by zero'");
            System.out.println("感谢使用本程序! ");
        }
    }
}

try-catch- finally语句块的执行流程大致分为如下两种情况。

(1)如果try语句块中所有语句正常执行完毕,finally 语句块也会被执行。例如,当在控制台输入两个数字时,示例4中的try语句块中的代码将正常执行,不会执行catch语句块中的代码,但是finally语句块中的代码将被执行。
(2)如果try语句块在执行过程中发生异常,无论这种异常能否被catch语句块捕获到,都将执行fnally语句块中的代码。例如,当在控制台输入的除数为 0时,示例4中的try语句块中将抛出异常,进入catch语句块,最后finally语句块中的代码也将被执行。

try-catch-finally结构中try语句块是必须存在的,catch、 finally 语句块为可选,但两者至少出现其中之一。
需要特别注意的是,即使在catch语句块中存在return语句,finally 语句块中的语句也会执行。发生异常时的执行顺序是,先执行catch语句块中return之前的语句,再执行fnally 语句块中的语句,最后执行catch语句块中的returm语句退出。

finally语句块中语向不执行的唯一情况是在异常处理代码中执行了System. exit(1)退出Java虚拟机,如示例5所示。
示例5
在try-catch-finally结构的catch语句块中执行System. exit(1)退出Java虚拟机。
关键代码:

import java.util.Scanner;

public class Test5 {
    public static void main(String[] args) {
        try {
            Scanner in = new Scanner(System.in);
            System.out.print("请输入被除数:");
            int num1 = in.nextInt();
            System.out.print(" 请输入除数:");
            int num2 = in.nextInt();
            System.out.println(String.format("%d / %d=%d", num1, num2, num1 / num2));
        } catch (Exception e) {
            System.err.println("出现错误:被除 数和除数必须是整数," + "除数不能为零");
            System.exit(1);//finally语句块不执行的唯一情况
            //return; //finally语句块仍旧会执行
        } finally {
            System.out.println("感谢使用本程序! ")
        }
    }
}

4. 使用多重catch处理异常

在计算并输出商的示例中,至少存在两异常情况,输入非整数内容和除数为0,在示例4中统按照Exception类型捕获,其实可使用多重catch语句块分别捕获并处理对应异常。

一段代码可能会引发多种类型的异常,这时,可以在一个try语句块后面跟多个catch 语句块分别处理不同的异常。但排列顺序必须是从子类到父类,最后一个一般都是Exception类。因为按照匹配原则,如果把父类异常放到前面,后面的catch 语句块将不会获得执行机会。

运行时,系统从上到下分别对每个catch语句块处理的异常类型进行检测,并执行第一个与异常类型匹配的catch语句。其中的一条catch语句之后,其后的catch语句将被忽略。对示例4进行修改,代码如示例6所示。
示例 6
使用多重catch处理异常。
关键代码:

import java.util.InputMismatchException;
import java.util.Scanner;
public class Test6 {
    public static void main(String[] args) {
        try {
            Scanner in = new Scanner(System.in);
            System.out.print("请输入被除数:");
            int num1 = in.nextInt();
            System.out.print("请输入除数:");
            int num2 = in.nextInt();
            System.out.println(String.format("%d 1 %d=%d", num1, num2, num1 / num2));
        } catch (InputMismatchException e) {
            System.err.println(" 被除数和除数必须是整数。");
        } catch (ArithmeticException e) {
            System.err.println("除数不能为零。");
        } catch (Exception e) {
            System.err.println(" 其他未知异常。");
        } finally {
            System.out.println(" 感谢使用本程序! ");
        }
    }
}

程序运行后,如果输入的不是整数,系统会抛出InputMismatchException异常对象。因此进入第一个catch 语句块,并执行其中的代码,而其后的catch语句块将被忽略。

如果系统提示输入被除数时输入“200”,系统会接着提示输入除数,当输入“0”时,系统会抛出ArithmeticException异常对象,因此进入第二个catch语句块并执行其中的代码,其他的catch语句块将被忽略。

提示

在使用多重catch语句块时,catch 语句块的排列顺序必须是从子类到父类,最后一个一般都是Exception类。下面的代码片断是错误的。

try{
        Scanner in=new Scanner(System.in);
        int totalTime=in.nextInt();
}catch(Exception e1){
        System.out.println(" 发生错误!");
}catch(InputMismatchException e2){
        System.out.println("必须输入数字!");
}

5.使用throws 声明抛出异常
如果在一个方法体中抛出了异常,并希望调用者能够及时地捕获异常,Java语言中通过关键字throws声明某个方法可能抛出的各种异常以通知调用者。throws 可以同时声明多个异常,之间由逗号隔开。

在下面的示例7中,把计算并输出商的任务封装在了dvide()方法中,并在方法的参数列表后通过throws声明抛出了异常,然后在main()方法中调用该方法,此时main()方法就知道dvide()方法中抛出了异常,可以采用如下两种方式进行处理。
➢通过try-catch捕获并处理异常。
➢如果调用者不知道如何处理该异常,可以继续通过throws声明异常,让上一级调用者来处理异常。main()方法声明的异常将由Java虚拟机来处理。
示例7
在Java程序中使用throws声明抛出异常。
关键代码:

public class Test7 {
    public static void main(String[] args) {
        try {
            divide();
        } catch (InputMismatchException e) {
            System.err.println("被除数和除数必须是整数。");
        } catch (ArithmeticException e) {
            System.err.println("除数不能为零。");
        } catch (Exception e) {
            System.err.println("其他未知异常。");
        } finally {
            System.out.println("感谢使用本程序! ");
            //通过throws声明抛出异常
            public static void divide () throws Exception {
                Scanner in = new Scanner(System.in);
                System.out.print("请输入被除数:");
                int num1 = in.nextInt();
                System.out.print(" 请输入除数:");
                int num2 = in.nextInt();
                System.out.println(String.format("%d 1 %d=%d", num1, num2, num1 / num2));
            }
        }
    }
}

6.使用throw抛出异常
除了系统自动抛出异常外,在编程过程中,有些问题是系统无法自动发现并解决的,如年龄不在正常范围内,性别输入不是“男”或“女”等,此时需要程序员而不是系统来自行抛出异常,把问题提交给调用者去解决。

在Java语言中,可以使用throw关键字来自行抛出异常。在下面示例8的代码中抛出了一个异常,抛出异常的原因在于当前环境无法解决参数问题,因此在方法内部通过throw抛出异常,把问题交给调用者去解决。

示例8
在Java程序中使用throw抛出异常。
关键代码:

public class Person {
    private String name = "";//姓名
    private int age = 0;//年龄
    private String sex = " 男 ";
//设置性别

    public void setSex(String sex) throws Exception {
        if (" 男 ".equals(sex) || " 女 ".equals(sex))
            this.sex = sex;
        else {
            throw new Exception("性别必须是\"男\"或者\"女\"! ");
            //输出基本信息
            public void print () {
                System.out.println(this.name + "(" + this.sex + ", " + this.age + " 岁");
            }
        }
    }

    public class Test8 {
        public static void main(String[] args) {
            Person person = new Person();
            try {
                person.setSex("Male");
                person.print();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

注意
throw和throws的区别如下。
(1)作用不同 : throw 用于程序员自行产生并抛出异常,throws用于声明该方法内抛出了异常。
(2)使用的位置不同:throw位于方法体内部,可以作为单独语句使用;throws 必须跟在方法参數列表的后面,不能单独使用。
(3)内容不同: thow抛出一个异常对象,只能是一个; thows后面跟异常类,可以跟多个。

7.自定义异常

当JDK中的异常类型不能满足程序的需要时,可以自定义异常类。使用自定义异常一般有如下几个步骤。
(1)定义异常类,并继承Excepion或者RuntimeException。
(2)编写异常类的构造方法,并继承父类的实现,常见的构造方法有如下4种形式。

//构造方法1
public MyException() {
super();
}
//构造方法2
public MyException(String message) {
super(message);
}
//构造方法3
public MyException(String message, Throwable cause) {
super(message, cause);
}
//构造方法4
public MyException(Throwable cause) {
super(cause);
}

(3)实例化自定义异常对象,并在程序中使用throw 抛出。
示例9使用自定义异常实现示例8。
关键代码:

public class GenderException extends Exception {
//构造方法
}
//Person类相关代码
pubic void setSex(String sex)throws GenderException{
        if("男".equals(sex)||"女".equals(sex))
        this.sex=sex;
        else{
        throw new GenderException("性别必须是\"男\" 或者\"女\"!");
        }
}
//测试类
public static void main(String[]args){
        Person person=new Person();
        try{
        person.setSex("Male");
        person.print();
        }catch(GenderException e){
        e.printStackTrace();
        }
}

输出结果与示例8一致。

8.异常链

在异常处理时有时会遇到如下情况: A方法调用B方法,B方法却抛出了异常。那么A方法是继续抛出原有的异常还是抛出一个新异常呢?若抛出原有的异常将是很糟糕的设计方法。因为A方法与B方法进行了关联,不便于代码的修改和扩展。若抛出新的异常,虽然解决了A方法和B方法的关联问题,但是原有的异常信息却会丢失。幸运的是,JDK 1.4推出了异常链,正好解决这个问题。它虽然创建了新的异常,但却保留了原有异常的信息。
至此本章的任务就完成了。本章主要介绍了Java异常处理机制。异常处理机制已经成为主流编程语言的必备功能,它使程序的异常处理代码和业务逻辑代码分离,保证了程序代码的优雅性,提高了程序的健壮性、安全性和可维护性。在将来的开发中,能够对程序进行合理、及时地异常处理是对程序员的基本要求。

猜你喜欢

转载自blog.csdn.net/sanjiang521/article/details/107601964