1. 异常概述

在Java语言中,将程序执行中发生的不正常情况称为“异常”,开发过程中的语法错误和逻辑错误不是异常。Java程序在执行过程中所发生的异常事件可分为两类:

  • Error:Java 虚拟机无法解决的严重问题。如 JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError 和 OOM。一般不编写针对性的代码进行处理。
  • Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。这些问题常见如:
    • 空指针访问
    • 试图读取不存在的文件
    • 网络连接中断
    • 数组下标越界

Error 和 Exception 的结构如下图所示:
image.png
其中 checked 为编译时异常unchecked 为运行时异常。常见的 Error 和两种 Exception 如下所示
image.png


2. 异常处理概述

在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行 x/y 运算时,要检测分母为0、数据为空等可能错误。如果用过多的 if-else 分支会导致程序的代码冗长,可读性差,因此都采用异常处理机制。
Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。
Java的异常处理机制,用的是一个“抓抛模型”
【抛:】

  • 程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象(Java万物皆类,每个异常都对应一个类),并将此对象抛出,抛给程序的调用者。
  • 一旦抛出对象后,其后的代码就不再执行。

【抓:】

  • 也就是异常处理方式,有两种:
    • try-catch-finally
    • throws

3. 处理机制一:try-catch-finally

  1. try{
  2. // 可能出现的异常
  3. }catch(异常类型1 变量名1){
  4. // 处理异常1的方式
  5. }catch(异常类型2 变量名2){
  6. // 处理异常2的方式
  7. }
  8. ....
  9. finally{
  10. // 一定会执行的代码
  11. }

3.1 try-catch

当 try 中代码任意一行出现异常后,try内部该行代码之后就都不会执行了,跳转到对应catch处进行处理,直到执行 finally(可选)。之后,继续执行整个 try-catch-finally 结构后面的代码。<br />举个例子,用 String 向 Int 型转换异常来演示:
package pkg8;

public class Test {
    public static void main(String[] args) {
        String str = "abc" ;
        try {
            int num = Integer.parseInt(str);
            System.out.println("try内部代码");
        } catch (NumberFormatException exception){
            System.out.println(exception);
        }

        System.out.println(" try-catch-finally 结构外的代码");
    }
}

image.png
但是,如果 catch 里的异常类型与发生的异常类型不匹配,则依然会出错。
image.png
所有的异常类都继承于 Exception,因此只要 catch 中异常类型用 Exception,就能够匹配。
image.png
为了更直观的显示错误信息,catch 中一般都会调用 Exception 中的 getMessage() 方法,它会返回一个 String 型的异常信息。

try {
    int num = Integer.parseInt(str);
    System.out.println("try内部代码");
} catch (Exception exception){
    System.out.println(exception.getMessage());
}

image.png

3.2 finally

finally指一定会执行的代码,可选。看上去很鸡肋,但是在一些情况下是必要的,他的一定执行性体现如下:

  • 即使 try 中没有异常,finally 依然执行
  • 即使 try 中没有异常且有 return,finally 照样依然执行
  • 如果 catch 中出现了异常,finally 依然执行
  • 即使 catch 中没有异常且有 return,finally 照样依然执行

举个例子:

package pkg8;

public class Test {
    public static void main(String[] args) {
        String str = "abc" ;
        int num ;
        try {
            num = Integer.parseInt(str);
            System.out.println("try内部代码");
        } catch (Exception exception){
            System.out.println(exception.getMessage());
            num = Integer.parseInt(str) ;  // catch 中有异常
        } finally {
            System.out.println("finally 处代码");
        }

        System.out.println(" try-catch-finally 结构外的代码");
    }
}
运行结果为:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2643809/1626677474113-6745a5b4-cfcf-4f77-87af-1d49976ae20a.png#clientId=uaab51236-861e-4&from=paste&height=226&id=u2b6530c3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=226&originWidth=831&originalType=binary&ratio=1&size=39246&status=done&style=none&taskId=u18ec2043-c1f1-4ce2-9fad-b0b1158d781&width=831)<br />可以看到,由于 catch 中的异常没有处理,程序出错,try-catch-finally 结构外的代码不再执行,但是 finally 内部的代码依然被执行了,这就是 finally 的“一定会被执行”特性。

【什么时候用 finally?】

  • 像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动回收的,我们需要自己手动的进行资源的释放。此时资源释放的操作,就需要放在 finally 中执行。

    4. 处理机制二:throws + 异常类型

  • “throw + 异常类型”写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。一旦方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此类对象满足 throws 后的类型的话,就会被抛出。异常代码后续的代码就不再执行
  • 所谓抛出,就是交给其调用者处理,但是调用者到底有没有处理异常就不一定了。
  • throws 只是将异常抛给调用者了,也就是甩锅,并没有真正处理掉异常。只有 try+catch+finally 才算真正处理掉异常了。

举个例子:

package pkg8;

public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        test.method2();
    }

    public void method2(){
        try {
            method1();
        }catch (Exception e){
            System.out.println("method2 处理了 method1 的异常");
        }
    }

    public void method1 () throws Exception {
        String str = "abc";
        int num;
        num = Integer.parseInt(str);
    }
}
上述代码的处理机制就是:异常存在于 method1 中,但是 method1 并没有处理掉该异常,而是将异常通过 throws 抛出给了其调用者。而 method2 作为 method1 的调用者,就要通过  try-catch-finally 来处理掉该异常。

5. throw 关键字

throw 关键字也用来抛出异常(不处理),它和 throws 的功能是一样的。只不过 throws 要跟在方法的声明之后,而 throw 可以单独作用。
throws 抛出一个异常类,而 throw 直接抛出一个异常对象,其使用格式为:

throw 异常类型对象 ;
比如,先定义一个自定义异常类,继承于运行时异常 RuntimException,构造器选择String单参数构造器:
package pkg8;

public class MyException extends RuntimeException{

    public MyException(String message) {
        super(message);
    }

}

然后,method1 中,如果try体内发生了NumberFormatException,通过 throw 就可以抛给调用者处理,如果发生了NullPointerException,就自己处理。另外,还自定义了种逻辑异常,如果 a!= b,则抛出自定义的异常对象给调用者处理。

package pkg8;

public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        test.method2();
    }

    public void method2(){
        try {
            method1(10,20);
        }catch (Exception e){
            System.out.println("method2 处理了 method1 的异常");
        }
    }

    public void method1(int a,int b) {
        String str = "abc";
        int num ;
        try{
            num = Integer.parseInt(str);

        }catch(NullPointerException e){

        }
        if(a!=b)
            throw new MyException("自定义异常");
    }
}

image.png