Java基础6—异常机制

1. 引言

1.1 为什么要提出异常

在异常的概念出现之前,我们通常使用条件控制或syserror的方式来处理异常,并且程序执行过程中一旦出现错误,正序就会报错并停止,这样代码就非常不健壮。

为此,许多高级语言都引入了异常处理机制,此机制可能处理出现的异常而不会停止整个服务,让代码变得更健壮。

异常链

程序会在捕获一个异常后抛出另外一个异常,并且希望把异常原始信息保存下来,这被称为异常链。

1.2 Java的异常

java语言在设计之初,就提出了异常处理的框架,并且在1.4版本后增加了异常链机制,从而便于跟踪异常。Java语言允许你划定异常出现的范围,并给出修正机会,使得程序不因异常而终止或发生其他改变,并且Java能够保存异常信息,通过异常信息能够快速定位问题所在。

掌握异常处理可以让代码更健壮和易于维护,是程序员必须掌握的技能。下面是Java异常的一般定义形式:

try{
    // 可能出现异常的代码块
}catch(Exception e){
    // 出现异常才会进入
}finally(){
   	// 无论如何都会进入
}

那么,程序在执行过程中会出现哪些异常呢? 我们必须要处理吗?

在编写Java代码时,应当尽可能去避免错误和异常发生,但是有不可避免、不可预测的情况则发生在代码执行过程中,我们主要关注运行时出现的异常,并对其进行处理

2. Java异常机制

2.1 Java异常分类

在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类 Exception(异常)和 Error(错误)。Exception 能被程序本身处理(try-catch), Error 是无法处理的(只能尽量避免)。

Error

Error是程序无法处理的,比如内存溢出OutOfMemoryError、线程死亡ThreadDeath等,出现错误一般交由JVM处理,而JVM一般会终止线程。

Exception

Exception是程序可以处理的异常,它又分为受检异常(CheckedException)、UncheckedException(不受检异常)。

  • CheckedException一般发生在程序编译阶段,代码编译时可以检查出来(如类型错误转换、1/0等)、,必须要使用异常机制来处理,如try-catch来处理。
  • UncheckedException发生在运行期,具有不确定性,主要是由于程序的逻辑问题所引起的(比如在文件输入流时突然删除需要的文件),难以排查。

下面是Java源码的异常框架图:

在这里插入图片描述

实际上,由于Java具有继承特性,使用者还可以根据需求进行异常自定义

自定义异常:

public class GlobalException extends RuntimeException {
    @Getter
    @Setter
    private String msg;

    public GlobalException(String message) {
        this.msg = message;	// 对信息进行增强等
    }
}

##2.2 异常处理

2.2.1 try、catch、finally

前面已经提到,java语言将可能出现异常的代码块包括在try语句中,jvm在程序执行过程中检查异常语句,如果确保出现异常,则会进入符合条件的catch语句,然后进入finally语句,注意,无论如何,try执行完成后都会进入finally语句。

在以下 3 种特殊情况下,finally 块不会被执行:

  • tryfinally块中用了 System.exit(int)退出程序。但是,如果 System.exit(int) 在异常语句之后,finally 还是会被执行

  • 程序所在的线程死亡。

  • 关闭 CPU。

2.2.2 异常链

Java在1.4时提出了异常链机制,通过此机制能够更好地实现异常跟踪

异常跟踪

Exception类有一个printStackTrace()方法,它能够从发生异常的方法中输出堆栈信息,默认输出位置是System.err。但是我们现在需要追踪异常,Java允许我们将异常信息包装并重新抛出到其他位置,比如String字符串中,然后我们就可以在任何地方使用此字符串。

2.2.3 throw

Java允许使用者创建一个异常并抛出,从而使程序更健壮。

try {
    int a = 0;
    if(a == 0){
        throw new RuntimeException();
    }
}catch (Exception e){
    System.out.println("a不能为0");
}

2.2.4 try-with-resources

面对必须要关闭的资源,我们总是应该优先使用 try-with-resources 而不是try-finally。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。try-with-resources语句让我们更容易编写必须要关闭的资源的代码,若采用try-finally则几乎做不到这点。

//读取文本文件的内容
        Scanner scanner = null;
        try {
            scanner = new Scanner(new File("D://read.txt"));
            while (scanner.hasNext()) {
                System.out.println(scanner.nextLine());
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (scanner != null) {
                scanner.close();
            }
        }

// java7之后
try (Scanner scanner = new Scanner(new File("test.txt"));资源2;资源3) {
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException fnfe) {
    fnfe.printStackTrace();
}

2.3 如何使用异常

异常处理的目标

  • 恢复

  • 报告

异常处理的一般逻辑

  • 如果自己知道怎么处理异常,就进行处理;
  • 如果可以通过程序自动解决,就自动解决;
  • 如果异常可以被自己解决,就不需要再向上报告。

3. 异常的底层原理

3.1 异常是怎么抛出的?

public class Test_3 {
    public static void main(String[] args) {
        try{
            int a = 1 / 0;
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

我们可以看到,在最下面出现一个异常表,这就是JVM用来处理异常的,如果在[0,4)中发生异常的话,直接进到语句7,然后调用异常处理方法。

因为如果 JVM 中一个方法编译后的代码正好是 65535 字节长,并且以一条 1 字节长的指令结束,那么该指令就不能被异常处理机制所保护。

  • 如果出现异常了,JVM 会在当前的方法中去寻找异常表,查看是否该异常被捕获了。

  • 如果在异常表里面匹配到了异常,则调用 target 对应的索引下标的指令,继续执行。

  • 如果在异常表里没匹配到异常,jvm直接抛出此因异常,而不调用任何一个catch子句中的代码

3.2 finally为什么一定会执行?

public class MainTest {
   public static void main(String[] args) {
       try {
           int a = 1 / 0;
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           System.out.println("final");
       }
   }
}

在这里插入图片描述

上面可以看到,一个带有 finally 子句的 try 语句被编译为有一个特殊的异常处理程序,这个异常处理程序可以处理在 try 语句中抛出的(any)任何异常

在源码中,只在 finally 代码块出现过一次的输出语句,在字节码中出现了三次。

finally 代码块中的代码被复制了两份,分别放到了 try 和 catch 语句的后面。也就是无论是否发生异常、发生什么异常, finally 语句一定会被执行的效果

finally中为什么不能return?

public class Test_3 {
    public static void main(String[] args) {
        try{
            int a = 1 / 0;
        }finally {
            System.out.println(222);
            return;
        }
    }
}

// 当finally中书写return时,异常并不会被抛出

在这里插入图片描述

其实已经一目了然了。

右边的 finally 里面有 return,并没有 athrow 指令,所以异常根本就没有抛出去。

这也是为什么建议大家不要在 finally 语句里面写 return 的原因之一。

3.3 异常日志是怎么打印的?

在这里插入图片描述

上面这个异常日志是怎么打印的呢?

我们找到这个方法,可以看到这个方法只能被JVM调用,并且也可以通过重写此方法。

// java.lang.Thread#dispatchUncaughtException

/**
 * Dispatch an uncaught exception to the handler. This method is
 * intended to be called only by the JVM.
 */
private void dispatchUncaughtException(Throwable e) {
    getUncaughtExceptionHandler().uncaughtException(this, e);
}

public void uncaughtException(Thread t, Throwable e) {
    if (parent != null) {
        parent.uncaughtException(t, e);
    } else {
        Thread.UncaughtExceptionHandler ueh =
            Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) {
            ueh.uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) {
            System.err.print("Exception in thread \""
                             + t.getName() + "\" ");
            e.printStackTrace(System.err);
        }
    }
}
public class Test_3 {
    public static void main(String[] args) {
        Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(2222+e.getMessage());
            }
        });
        
        int a = 1 / 0;
        
    }
}

3.4 异常语句中的return

public static int test(){
    try {
        int a = 1/0;
        return 1;	// 不会执行,发生异常时直接catch了
    }catch (Exception e){
        return 2;	// 返回2
    }
}

public static int test(){
    try {
        int a = 1/0;
        return 1;	// 不会执行,发生异常时直接catch了
    }catch (Exception e){
        // 编译器报错,因为没有返回语句
    }
}

public static int test(){
    try {
        int a = 1/1;
        return 1;	// 返回1,没发生异常,不会执行catch
    }catch (Exception e){
        return 2;
    }
}
public static int test(){
    try {
        int a = 1/0;
        return 1;
    }finally {
        return 3;	// 返回3,异常不会处理,但也不会抛出,与class字节码相关
    }
}

public static int test(){
    try {
        int a = 1/0;
        return 1;
    }catch (Exception e){	// 异常被捕获
        return 2;
    }finally {
        return 3;	// 返回3,
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值