【Java面向对象】Exception 异常处理(捕获异常)

简单介绍这篇blog

先用代码举个异常的例子,再结合例子进行理论介绍。

对异常有大致了解后,再对代码进行优化。

理论和举例分析

什么是异常(Exception)?

异常是在程序中导致程序中断的一种指令流。

举个例子:程序中有100行代码,那么它需要按顺序执行第1行到第100行。如果走到第5行的时候出现了bug,从第五行以后的内容就不再执行了,程序崩溃了。那么这个导致程序崩溃(程序中断)的东西就是我们所说的异常。

例如,有如下操作的代码:

代码一:程序中无异常处理

import java.util.Scanner;

public class Demo {
    public static void main(String[] args){
        test();
        System.out.println("程序执行完毕 , 正常结束");
    }

    private static void test() {
        Scanner input = new Scanner(System.in);
        System.out.println("请输入一个数字");
        int x = input.nextInt();
        System.out.println("请再输入一个数字");
        int y = input.nextInt();
        System.out.println(x / y);
    }
}

代码一运行时可能会出现的异常

程序一可能会出现以下两种异常。

异常一:输入参数类型错误导致的异常

该程序需要接收的值是int类型的。如果用户输入为字母,就会出现如下异常:
Exception1

异常二:除数不能为零的算术异常

我们都知道除数不能为零。那么,如果在程序一中,用户输入的除数为0,就会出现如下异常:
Exception2

确定异常出现的位置

以上两个截屏中,红色字体第一行描述的是代码出现异常的原因。
那如何确定一场具体出现在哪个指令呢?可以看到图上有描述一些文件的行数。其中灰色表示的文件来自于依赖库,与我们自己写的代码无关,而蓝色的才是自己写的代码最初发生异常的位置

异常是怎么发生的?

这里用异常二来举例分析。

分析异常二

代码一中第15行出错,也就是x除以y的时候出错。

首先,要知道main方法的调用是JVM在执行,且代码出错以后是被JVM发现的。

当JVM在进行程序员所指定的指令执行时,发现这个指令不合理。那么它就需要告诉程序员:这条指令不合理。

然后,JVM就会通过程序中断(不再执行后续代码)的方式告诉程序员代码出现异常。

  • Java官方也对异常问题进行一些整理,列了很多的异常的类型(异常体系结构部分会举例)。为什么是类型呢?因为Java是一门纯面向对象的语言,它是通过对象来告诉程序员程序出现异常。再来解释一下异常发生的过程:

首先,JVM发现程序出现了除数为零这样的一个异常情况。

在执行第15行指令时,JVM会创造一个对象,叫算术异常对象。这个异常对象里面描述的信息就是除数不能为零。

然后,JVM就会中断程序,并显示上面图片上的红色信息,也就是把这个对象给到程序员。

这就是面向对象的一个异常处理的流程(附上图便于理解)。
在这里插入图片描述

如果再具体一点来描述。

JVM创建这个异常对象,就相当于这个第15行代码的x除y这句话,变成了一个new对象的操作,new出来的是一个异常对象,因为JVM在执行这句指令的时候出错了。这个异常对象被创建以后,如果在这个main方法里并没有被处理掉,就又被通过类似返回的方式(类似返回,不是返回)抛回给调用这个main方法的人,也就是抛回给JVM。

  • 相当于JVM产生了一个异常对象给程序员,但没有被程序员处理,就又通过类似返回的方式给回了JVM。
    JVM发现这个异常对象回到它这以后,它的处理方案就是前面所说的中断程序。

那么我们怎么才能不让这个异常对象要返回JVM,让程序正常运行?这个拦截异常对象返回JVM的过程就是接下来要讲的异常处理。

异常处理

程序员对异常处理的拦截操作就叫做捕获异常,可以通过try…catch…来实现。标准异常处理格式如下:

try{
	// 可能会发生异常的一段代码
}catch(异常类型1 对象名1){
	// 异常类型1的处理操作
}catch(异常类型2 对象名2){
	// 异常类型2的处理操作
} ...
finally{
	// 异常的统一出口
}

try…catch…处理流程

  1. 一旦产生异常,则系统会自动产生一个异常类的实例化对象。
  2. 那么,此时如果异常发生在try语句,则会自动找到匹配的catch语句执行,如果没有在try语句中,则会将异常抛出.
  3. 所有的catch根据方法的参数匹配异常类的实例化对象,如果匹配成功,则表示由此catch进行处理。

也就是说,这里如果try{…}中出现异常,不会像前面说的那样抛给JVM,而是在对应异常类型catch{…}里面进行异常处理做补救操作。在try{…}这块运行的过程中,一旦发生某个异常被程序员捕获后,try{…}瞬间结束执行,直接跳入catch{…}开始执行。但是程序员必须先预料到哪个异常可能会发生,才能对这个异常进行处理让操作。

那具体都有哪些异常呢?后面异常体系结构部分会描述。

finally

在进行异常的处理之后,在异常的处理格式中还有一个finally语句,那么此语句将作为异常的统一出口,不管是否产生了异常,最终都要执行此段代码。

无论是否发生异常,finally必然执行。

只有在执行finally的指令前,电脑关机了,没内存了,或者已经通过代码System.exit(status:0);退出了JVM等特殊情况下,finally才不会执行。

(在最后补充部分举例)

异常体系结构

异常指的是Exception, Exception类, 在Java中存在一个父类Throwable(可能的抛出)。
Throwable存在两个子类:

  1. Error:表示的是错误,是JVM发出的错误操作,只能尽量避免,无法用代码处理。
  2. Exception:一般表示所有程序中的错误,所以一般在程序中将进行try…catch…的处理。
    Exception architecture

Exception类

Exception类中有一个比较特殊的异常子类叫做执行时异常(RuntimeException)。它又被称为非受检异常,指的是这个代码在执行过程中因为参数问题有可能发生的错误。

比如,前面的异常一和异常二。

总的来说,Exception类分两种:受检异常和非受检异常。( 面试可能会问到:Java异常类型分哪些

受检异常,即除RuntimeException的其他子类。代码写出来的代码会飘红。

非受检异常RuntimeException,在代码运行时才有可能发生问题。

补充

代码一的优化及其他例子

代码一的优化及其他例子。

代码二(代码一改进):加上异常一和异常二处理的代码

import java.util.Scanner;

public class Demo2 {
    public static void main(String[] args){
        test();
        System.out.println("程序执行完毕 , 正常结束");
    }

    private static void test() {
        Scanner input = new Scanner(System.in);
        System.out.println("请输入一个数字");
        int x = input.nextInt();
        System.out.println("请再输入一个数字");
        int y = input.nextInt();
        try {
            System.out.println(x / y);
            System.out.println("处理完毕");
        }catch(InputMismatchException e){
            System.out.println("请输入输入数字");
        }catch(ArithmeticException e) {
            System.out.println("除数不能为0");
        }
    }
}

程序运行:
Demo2

这里对比代码一的运行异常结果,可以看出,在出现异常后程序中断,System.out.println("程序执行完毕 , 正常结束");这句指令没有执行。代码二优化后,该指令能够被执行。

如果希望两个异常都执行同意指令,也可以将两个catch合并成一个。

catch(InputMismatchException|ArithmeticException e) {
    System.out.println("出现异常");
}

代码三:一个处理异常更好的例子

当遇到异常时,不是直接打印出错,而是及时补救。

import java.util.InputMismatchException;
import java.util.Scanner;

public class Demo3 {
    public static void main(String[] args) {
        int num = menu();
        System.out.println("你输入的是:"+num);
    }

    public static int menu(){
        System.out.println("1. 增加XX");
        System.out.println("2. 删除XX");
        System.out.println("3. 修改XX");
        System.out.println("0. 退出");
        System.out.println("请根据提示,选择功能序号:");

        Scanner input = new Scanner(System.in);
        int num = -1;
        try{
            num = input.nextInt();
            if(num<0 || num>3){
                //程序有问题 , 输入有误
                System.out.println("功能序号必须是: 0/1/2/3");
                return menu();
            }
            return num;
        }catch(InputMismatchException e){
            //补救
            System.out.println("必须输入数字哦");
            return menu();
        }
    }
}

这样可以让用户再次输入,直到输入正确。
 Demo3

finally的几个的例子

代码四:try中的return是否会执行finally?

public class Demo4 {
    public static void main(String[] args) {
        test();
    }
    public static void test(){
        try{
            System.out.println("1");
            System.out.println("2");
            System.out.println("3");
            System.out.println("4");
            return;
        }catch(Exception e){
        }finally {
            System.out.println("finally语句执行");
        }
    }
}

运行结果:

1
2
3
4
finally语句执行

简单解释一下:会执行finally,finally在return准备到方法真正结束的中间执行。

代码五:在finally改变某个值时,可能会出现什么结果?

public class Demo5 {
    public static void main(String[] args) {
        Person p = test();
        System.out.println(p.age);
    }
    public static Person test(){
        Person p = new Person();	//赋值了person的引用地址
        try{
            p.age = 18;
            return p;
        }catch(Exception e){
            return null;
        }finally {
            p.age = 28;
        }
    }
    static class Person{
        int age;
    }
}

运行结果:

28

简单来说就是pass-by-reference。这里p储存的是地址,返回的是地址。

代码六:在finally改变某个值时,还可能会出现什么结果?

public class Demo6 {
    public static void main(String[] args) {
        int a = test();
        System.out.println(a);
    }
    public static int test(){
        int a = 18;
        try{
            return a;
        }catch(Exception e){
        }finally {
            a = 28;
        }
        return 0;
    }
    static class Person{
        int age;
    }
}

运行结果:

18

简单来说就是pass-by-value。在执行try时,return的就是18。

代码五和代码六的区别其实就是 pass-by-value 和 pass-by-reference 的区别。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值