异常机制
基本概念
概念: 程序执行中发生的不正常情况(语法错误和逻辑错误不是异常)
java异常机制可以使程序中异常处理代码和正常业务代码分离,提高程序健壮性
Throwable
Throwable 是 Java 语言中所有错误与异常的超类
Throwable 包含两个子类:Error(错误)和 Exception(异常)如下图
Error(错误)
Error 类及其子类:程序中无法处理的错误,表示运行应用程序中出现了严重的错误
此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程
这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误
Exception(异常)
异常分为两大类:运行时异常,编译时异常
- 运行时异常,编译器不要求强制处置的异常(一般是逻辑错误),运行时异常和错误为非可查异常,不需要处理
- 编译时异常是编译器要求必须处置的异常
编译异常
概念:编译异常又可以称为可查异常,编译异常是RuntimeException以外的异常,是编译器要求必须处置的异常
| 异常类型 | 发生的场景 |
|---|---|
| SQLException | 操作数据库时,查询表可能发生异常 |
| lOException | 操作文件时,发生的异常 |
| FileNotFoundException | 当操作一个不存在的文件时,发生异常 |
| ClassNotFoundException | 加载类,而该类不存在时,异常 |
| EOFException | 操作文件,到文件末尾,发生异常 |
| IllegalArguementException | 参数异常 |
运行时异常
运行时异常,编译器不要求强制处置的异常(一般是逻辑错误)
空指针异常
NullPointException :当应用程序在需要使用对象的地方使用null,抛出该异常
// 实例
class NullPointException{
public static void main(String[] args) {
String name = null;
System.out.println(name.length()); // NullPointerException
}
}
数字运算异常
ArithmeticException:当出现异常运算条件时,抛出异常
// 实例
class ArithmeticException {
public static void main(String[] args) {
int num1 = 10,num2 = 0;
int div = num1/num2; // ArithmeticException
}
}
数组下标越界
ArrayIndexOutOfBoundsException:用非法索引访问数组的异常
// 实例
class ArrayIndexOutOfBoundsException {
public static void main(String[] args) {
int[] array = new int[3];
System.out.println(array[3]); //ArrayIndexOutOfBoundsException
}
}
类型转换异常
ClassCastException:当试图将对象强制类型转换为不是该实例的子类,抛出该异常
// 实例
class ClassCastException {
public static void main(String[] args) {
A b = new B();
C c = (C)b; // ClassCastException
}
}
class A{}
class B extends A{}
class C extends A{}
数字格式不正确异常
NumberFormatException:当应用程序试图将字符串转换成一种数值类型,将抛出该异常
// 实例
class NumberFormatException {
public static void main(String[] args) {
String name = "";
int num = Integer.parseInt(name); // NumberFormatException
}
}
异常处理
异常处理关键字
- try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
- catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。
- finally – finally语句块总是会被执行。它主要用于回收在try块里打开的资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
- throw – 用于抛出异常。
- throws – 用在方法签名中,用于声明该方法可能抛出的异常。
两种处理方式
1.try - catch - finally (程序员在代码中捕获异常,自行处理)
注意:try、catch和finally都不能单独使用,只能是try-catch、try-finally或者try-catch-finally
// try - catch - finally格式如下
try{
// 代码可能有异常
// 如果没有出现异常,则执行try块中所有的语句,反之有出现异常,则不再执行try块中剩余的语句,抛出异常
}catch(NullPointerException e){
// 捕获异常
// 1.当异常发生时,才会执行catch内的代码
// 2.系统将异常封装成Exception对象e,传递给catch
// 3.同一个 catch 也可以捕获多种类型异常,用 | 隔开
}catch(RuntimeException e){
// 可以有多个catch语句进行捕获不同的异常,要求子类异常在父类异常之前,不然就没有子类异常存在的意义
}finally{
// 不管是否有异常,一般都会执行
// 总结:finally遇见如下情况不会执行
// 1.在前面的代码中用了System.exit()退出程序。
// 2.finally语句块中发生了异常。
// 3.程序所在的线程死亡。
// 4.关闭CPU。
}
// 可以进行try-finally配合使用,不进行其他异常捕获(包括throws),不管是否有异常都执行某些语句,然后程序退出
try{
}finally{
}
2.throw或者throws 将异常抛出(交给调用者来处理,最顶级处理者JVM)
| throws VS throw | 意义 | 位置 | 后面的语句 |
|---|---|---|---|
| throws(异常的申明) | 异常处理的一种方式 | 方法声明处 | 异常类型 |
| throw(异常的抛出) | 手动生成异常对象的关键字 | 方法体中 | 异常对象 |
测试代码
// throws(异常的申明)
class Father{
// RuntimeException 是 NullPointerException 的父类
// 运行时异常:如果没有处理,默认throws的方式处理
public void methos() throws RuntimeException{}
}
class Son extends Father{
// 子类重写的方法,所抛出的异常类型要么和父类抛出的异常一样,要么为父类抛出异常的子类型
@Override
public void methos() throws NullPointerException{
super.methos();
}
}
// throw (异常的抛出)
public double method(int num,int value) {
if(value == 0) {
throw new ArithmeticException("参数不能为0"); // 抛出一个运行时异常
}
return num / value;
}
throw或者throws 抛出形式:
自定义异常
继承Throwable的子类或者间接子类
运行时异常通常继承RuntimeException;编译时异常通常继承Exception
// 例子
class test{
public static void main(String[] args) {
int age = 208;
/*
* Exception in thread "main" com.Al_tair.exception_.AgeJudge: 不符合年龄
* at com.Al_tair.exception_.test.main(Exception01.java:71)
*/
if(!(age >= 0 && age <= 140)){
throw new AgeJudge("不符合年龄");
}
}
}
class AgeJudge extends RuntimeException{
public AgeJudge(String message) {
super(message);
}
}
JVM处理异常的机制
异常表
异常发生时的机制如下 取自java全栈知识体系
- JVM会在当前出现异常的方法中,查找异常表,是否有合适的处理者来处理
- 如果当前方法异常表不为空,并且异常符合处理者的from和to节点,并且type也匹配,则JVM调用位于target的调用者来处理
- 如果上一条未找到合理的处理者,则继续查找异常表中的剩余条目
- 如果当前方法的异常表无法处理,则向上查找(弹栈处理)刚刚调用该方法的调用处,并重复上面的操作
- .如果所有的栈帧被弹出,仍然没有处理,则抛给当前的Thread,Thread则会终止
- 如果当前Thread为最后一个非守护线程,且未处理异常,则会导致JVM终止运行
// 测试代码 test.java
public class test{
public static int[] list = new int[2];
public static void main(String[]args){
try {
int a = list[2];
} catch (Exception e) {
e.printStackTrace();
}
}
}
# 反编译 test.class文件
public class test {
public static int[] list;
public test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #7 // Field list:[I
3: iconst_2
4: iaload
5: istore_1
6: goto 14
9: astore_1
10: aload_1
11: invokevirtual #15 // Method java/lang/Exception.printStackTrace:()V
14: return
Exception table: 异常表
# from 可能发生异常的起始点
# to 可能发生异常的结束点
# target 上述from和to之前发生异常后的异常处理者的位置
# type 异常处理者处理的异常的类信息
from to target type
0 6 9 Class java/lang/Exception
static {};
Code:
0: iconst_2
1: newarray int
3: putstatic #7 // Field list:[I
6: return
}