异常的介绍和体系结构

异常介绍

  • 指的是程序在编译或执行过程中,出现的非正常的情况(错误)
    • ArrayIndexOutOfBoundsException:索引越界异常
    • ClassCaseException:类型转换异常
    • NullPointerException:空指针异常
  • 注意:语法错误,不是异常

异常体系

Java中所有的异常和错误的父类是Throwable,它的两个子类为ErrorException

Error:

  • 严重级别问题
  • 常见的有栈内存溢出(StackOverflowError),堆内存溢出(OutOfMemoryError)

Exception:

  • RuntimeException及其子类:运行时异常
    • 数组索引越界异常、空指针异常、类型转换异常都属于运行时异常
    • 编译阶段没有错误,运行时可能会出现错误。这种错误通常都是程序员代码不严谨造成的
  • 除RuntimeException之外的所有异常:编译时异常
    • 编译阶段就出现的错误。主要起到提醒作用

异常的默认处理流程

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.norlcyan.exception;

public class ExceptionDemo01 {
/*
异常默认的处理方式:向上抛出
*/
public static void main(String[] args) {
System.out.println("main start...");
method(); // ③ main方法接收到异常对象,继续向上抛出 new ArithmeticException();
// ④ JVM虚拟机接收到异常对象,将异常的错误信息打印在控制台,将程序停止
System.out.println("main end...");
}

private static void method() {
System.out.println("method start...");
int i = 1 / 0; // ① 会在出现异常的位置,创建一个异常对象 new ArithmeticException();
// ② 将异常对象向上抛出,抛给main方法
System.out.println("method end...");
}
}

处理流程图:

异常处理方式

try…catch

  • 能够抛出的异常对象捕获,然后执行异常的处理方案

  • 好处:程序不会直接停止,而是执行异常的处理方案、

  • 格式:

    1
    2
    3
    4
    5
    try {
    可能会出现异常的代码
    } catch (异常类型1 变量) {
    处理异常的方案
    }
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class ExceptionDemo02 {
    public static void main(String[] args) {
    System.out.println("开始");
    try {
    int i = 1 / 0;
    } catch (ArithmeticException e) {
    System.out.println("[" + e + "] 捕获了异常...");
    System.out.println("异常原因:" + e.getMessage());
    e.printStackTrace(); // 输出详细异常信息,不会中断程序执行
    }
    System.out.println("结束");
    }
    }

异常的所有方法都在父类Throwable中实现了,并且只需要记住两个方法

getMessage:获取到发生异常的原因

printStackTrace:输出详细异常信息,不会中断程序异常

当异常对象不匹配catch中的类型时,是不会执行异常处理方案的。当需要捕获多种异常时,可以创建多个catch语句或使用异常类型分隔符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
try {
int[] arr = null;
System.out.println(arr[10]);
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(e.getMessage());
} catch (NullPointerException e) {
System.out.println(e.getMessage());
}

// 也可以简写成:
try {
int[] arr = null;
System.out.println(arr[10]);
} catch (ArithmeticException | ArrayIndexOutOfBoundsException | NullPointerException e) {
System.out.println(e.getMessage());
}

当使用异常类型分隔符时,最终编译的字节码文件中会转换为多个catch

如果异常类型为Exception(所有异常的父类),那么要在最后catch,使用异常类型分隔符也必须只能是兄弟类型

throws抛出

  • throws:用在方法上,作用是声明,声明这个方法中的异常是抛出处理
  • 格式:public void method() throws 异常1, 异常2, 异常3 ... {}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.io.FileNotFoundException;
import java.io.FileReader;

public class ExceptionDemo05 {
public static void main(String[] args) throws Exception {
method();
}

public static void method() throws FileNotFoundException, ClassNotFoundException {
System.out.println("开始");
FileReader fr = new FileReader("D:\\abc.txt");
System.out.println("结束");

Class.forName("com.norlcyan.exception.Student");
}
}

// 在继承关系中,子类重写父类方法,不能抛出父类中不存在的,或者比父类更大的异常
class Fu {
public void show() throws Exception {
System.out.println("Fu...Show...");
}
}

class Zi extends Fu{
@Override
public void show() throws FileNotFoundException, ClassNotFoundException {
FileReader fr = new FileReader("D:\\abc.txt");
Class.forName("com.norlcyan.exception.Student");
}
}

当某个方法抛出了异常,那调用这个方法的方法也需要抛出异常,且类型大小不能低于(>=)被调用方法的异常

当某个子类中重写后的成员方法抛出了异常,那它的父类也方法也需要抛出异常,且子类异常类型不能大于(<=)父类异常

throw关键字

这是用于抛出异常的关键字,使用方式就是throw new Exception(...),理解为抛出创建的异常对象

比如,当创建学生对象时,需要对年龄进行负数判断:

1
2
3
4
5
6
7
public void setAge(int age) throws Exception {
if (age >= 0 && age <= 100) {
this.age = age;
} else {
throw new Exception("年龄有误...");
}
}

try…catch…和throws

使用思路:当问题需要暴露给开发者时,优先使用throws。需要将问题捕获,且不影响程序运行,就选择try…catch

自定义异常

创建自定义异常,可以更加精准的捕获异常

自定义异常分类:

  1. 自定义编译时异常:
    • 定义一个异常类继承Exception
    • 重写构造器
  2. 自定义运行时异常:
    • 定义一个异常类继承RuntimeException
    • 重写构造方法

示例:

1
2
3
4
5
6
7
8
public class StudentAgeException extends Exception{
public StudentAgeException() {
}

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

抛出异常

1
2
3
4
public void setAge(int age) throws StudentAgeException {
...;
throw new StudentAgeException("...")
}

捕获异常

1
2
3
4
5
try {
...
} catch (StudentAgeException e) {
...
}

编译时异常(检查异常)- 必须声明throws

运行时异常(非检查异常)- 不需要声明throws

自定义异常的选择

场景 异常类型 是否需要throws
业务逻辑异常 Exception ✅ 必须声明
可恢复异常 Exception ✅ 必须声明
外部资源异常 Exception ✅ 必须声明
编程错误 RuntimeException ❌ 不需要声明
系统错误 RuntimeException ❌ 不需要声明
违反契约 RuntimeException ❌ 不需要声明