1. 异常
1.1 概述
(1) 认识异常
异常是指程序在运行过程中出现非正常情况。
(2) Java 异常体系结构

所有异常类都是 Throwable 类的子类,它派生出两个子类,Error 类和 Exception 类。
(1)Error 类 : 表示程序无法恢复的严重错误或者恢复起来比较麻烦的错误,例如内存溢出、动态链接失败、虚拟机错误等。应用程序不应该主动抛出这种类型的错误,通常由虚拟机自动抛出。如果出现这种错误,最好的处理方式是让程序安全退出。
(2)Exception 类 : 由Java应用程序抛出和处理的非严重错误,例如文件未找到、网络连接问题、算术错误(如除以零)、数组越界、加载不存在的类、对空对象进行操作、类型转换异常等。Exception类的不同子类对应不同类型的异常。。Exception类又可分为两大类异常:
不受检异常:也称为 unchecked 异常,包括RuntimeException及其所有子类。对这类异常并不要求强制进行处理,例如算术异常ArithmeticException等。受检异常:也称为 checked 异常,指除了不受检异常外,其他继承自Exception类的异常。对这类异常要求在代码中进行显式处理。
程序中常见异常
| 异常类名 | 分类 | 说明 |
|---|---|---|
| Exception | 设计时异常 | 异常层次结构的根类 |
| IOException | 设计时异常 | IO异常的根类,属于非运行时异常 |
| FileNotFoundException | 设计时异常 | 文件操作时,找不到文件,属于非运行时异常 |
| RuntimeException | 运行时异常 | 运行时异常的根类,RuntimeException及其子类不要求处理 |
| ArithmeticException | 运行时异常 | 算术运算异常,例如除数为零,属于运行时异常 |
| IllegalArgumentException | 运行时异常 | 方法接收到非法参数,属于运行时异常 |
| ArrayIndexOutOfBoundsException | 运行时异常 | 数组越界访问异常,属于运行时异常 |
| NullPointerException | 运行时异常 | 尝试访问null对象的成员时发生的空指针异常,属于运行时异常 |
| ArrayStoreException | 运行时异常 | 数据存储异常,写数组操作时,对象或数据类型不兼容 |
| ClassCastException | 运行时异常 | 类型转换异常 |
| IllegalThreadStateException | 运行时异常 | 试图非法改变线程状态,例如试图启动一已经运行的线程 |
| NumberFormatException | 运行时异常 | 数据格式异常,试图把字符串非法转换成数值 |
上述异常类按照设计时异常和运行时异常进行了分类,并提供了相应的说明。设计时异常通常属于编译时异常,需要在代码中进行处理或声明抛出;而运行时异常通常不要求强制处理,可以选择捕获和处理,也可以由调用者处理,如果没有处理,将会导致程序异常终止。
1.2 Java异常处理机制
1.2.1 异常处理
(1)用 try、catch、finally 来捕获和处理异常
try块中包含可能会抛出异常的代码catch块用于捕获并处理指定类型的异常finally块中的代码无论是否发生异常都会被执行,通常用于释放资源或清理操作
(2)用 throw、throws 来抛出异常
throw关键字用于手动抛出异常
public void method() throws CustomException {
if (condition) {
throw new CustomException("This is a custom exception");
}
}
throws关键字用于在方法中声明指定可以抛出的异常类型,表示该方法可能会抛出该类的异常,由调用者来处理
public void method() throws IOException, CustomException {
// 方法体
}
throw用于具体的异常对象,而throws用于异常类型的声明。
1.2.2 捕获异常
(1)try-catch 处理异常
public class ExceptionTryCatch {
public static void main(String[] args) {
try {
int i = 1,j = 0,res;
System.out.println("begin");
res = i / j;
System.out.println("end");
} catch (Exception e) {
System.out.println("caught");
e.printStackTrace();
}
System.out.println("over");
}
}
begin
caught
over
java.lang.ArithmeticException: / by zero
at kfm.bases.ErrorAndPxception.ExceptionTryCatch.main(ExceptionTryCatch.java:8)
try-catch语句块首先执行try语句块中的语句,这时可能会出现以下3种情况:
- 如果try语句块中的所有语句正常执⾏完毕,没有发⽣异常,那么catch语句块中的所有语句都将被忽略。
- 如果try语句块在执⾏过程中发⽣异常,并且这个异常与catch语句块中声明的异常类型匹配,那么try语句块中剩下的代码都将被忽略,⽽相应的catch语句块将会被执⾏。匹配是指catch所处理的异常类型与try块所⽣成的异常类型完全⼀致或是它的⽗类。
- 如果try语句块在执⾏过程中发⽣异常,⽽抛出的异常在catch语句块中没有被声明,那么程序⽴即终⽌运⾏,程序被强迫退出。
- catch语句块中可以加⼊⽤⼾⾃定义处理信息,也可以调⽤异常对象的⽅法输出异常信息,常⽤的⽅法如下:
void prinStackTrace():输出异常的堆栈信息。堆栈信息包括程序运行到当前类的执行流程,它将输出从方法调用处到异常抛出处的方法调用的栈序列。String getMessage():返回异常信息描述字符串,该字符串描述了异常产生的原因,是 printStackTrace() 输出信息的一部分。
(2)try-catch-finally 处理异常
try-catch-finally 语句块组合使用时,无论 try 块中是否发生异常, finally 语句块中的代码总能被执行。
public class TryCatchFinally {
public static void main(String[] args) {
try {
int i = 1,j = 0, res;
System.out.println("bregin");
res = i / j;
System.out.println("end");
} catch (ArithmeticException e) {
System.out.println("caught");
System.out.println(e.getMessage()); // 异常信息描述字符串
e.printStackTrace(); // 输出异常堆栈信息
System.out.println("1");
} finally {
System.out.println("finally");
}
System.out.println("over");
}
}
bregin
caught
/ by zero
java.lang.ArithmeticException: / by zero
at kfm.bases.ErrorAndPxception.TryCatchFinally.main(TryCatchFinally.java:8)
1
finally
over
try-catch-finally语句块执行流程大致分为如下两种情况。
- 如果
try语句块中所有语句正常执⾏完毕,程序不会进⼊catch语句块执⾏,但是finally语句块会被执⾏。 - 如果
try语句块在执⾏过程中发⽣异常,程序会进⼊到catch语句块捕获异常,finally语句块也会被执⾏。
try-catch-finally 结构中 try 语句块是必须存在的,catch 、finally 语句块为可选,但两者⾄少出现其中之⼀。
即使在
catch语句块中存在return语句,finally语句块中的语句也会执行。发生异常时的执行顺序是,先执行catch语句块中return之前的语句,再执行finally语句块中的语句,最后执行catch语句块中的return语句退出。
finally语句块中语句不执行的唯一情况是在异常处理代码中执行了System.exit(1),退出 java 虚拟机。
public class TryCatchFinallyExit {
public static void main(String[] args) {
try {
int i = 1,j = 0,res;
System.out.println("begin");
res = i / j;
System.out.println("end");
} catch (ArithmeticException e) {
System.out.println("caught");
e.printStackTrace();
System.exit(1);
} finally {
System.out.println("finally");
}
System.out.println("over");
}
}
begin
caught
java.lang.ArithmeticException: / by zero
at kfm.bases.ErrorAndPxception.TryCatchFinallyExit.main(TryCatchFinallyExit.java:8)
System.exit(1) 是一个 Java 中的方法调用,用于终止当前正在执行的 Java 虚拟机(JVM)进程。在调用 System.exit(1) 后,JVM 会立即退出,并返回一个指定的退出状态码 (1 在这里表示非正常退出),0 表示正常退出,非零表示非正常退出。
(3)使用多重 catch 处理异常
当一段代码可能引发多种类型的异常时,可以在一个try语句块后面跟随多个 catch 语句块,分别处理不同类型的异常。一旦系统执行了与异常类型匹配的 catch 语句块,并执行其中的一条 catch 语句后,其后的 catch 语句块将被忽略,程序将继续执行紧随 catch 语句块的代码。
catch语句块的排列顺序必须是从子类到父类,最后一个一般是Exception类。这是因为在异常处理中,catch语句块会按照从上到下的顺序进行匹配,系统会检测每个catch语句块处理的异常类型,并执行第一个与异常类型匹配的catch语句块。如果将父类异常放在前面,子类异常的catch语句块将永远不会被执行,因为父类异常的catch语句块已经处理了异常。
import java.util.InputMismatchException;
import java.util.Scanner;
public class MuchCatch {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("计算开始");
int i, j, res;
try {
System.out.println("请输入被除数");
i = input.nextInt();
System.out.println("请输入除数");
j = input.nextInt();
res = i / j;
System.out.println("结果为" + res);
} catch (InputMismatchException e) {
System.out.println("除数和被除数必须为整数");
} catch (ArithmeticException e) {
System.out.println("除数不能为0");
} catch (Exception e) {
System.out.println("其他异常" + e.getMessage());
} finally {
System.out.println("感谢使用本程序");
}
System.out.println("程序结束");
}
}
1.2.3 抛出异常
(1)使用 throws 声明抛出异常
try-catch-finally 处理的是在一个方法内部发生的异常,在方法内部直接捕获并处理。如果在一个方法体内抛出了异常,并希望调用者能够及时地捕获异常,Java 语言中通过关键字 throws 声明某个方法可能抛出的各种异常,以通知调用者。throws 可以同时声明多个异常,之间用逗号隔开。
import java.util.InputMismatchException;
import java.util.Scanner;
public class ThrowsExcepton {
public static void main(String[] args) {
try {
divide();
} catch (InputMismatchException e) {
System.out.println("除数和被除数必须为整数");
} catch (ArithmeticException e) {
System.out.println("除数不能为0");
} catch (Exception e) {
System.out.println("其他异常" + e.getMessage());
} finally {
System.out.println("感谢使用本程序");
}
System.out.println("程序结束");
}
// 通过 throws 声明抛出设计时异常
public static void divide() throws Exception {
Scanner input = new Scanner(System.in);
System.out.println("计算开始");
int i, j, res;
System.out.println("请输入被除数");
i = input.nextInt();
System.out.println("请输入除数");
j = input.nextInt();
res = i / j;
System.out.println("结果为" + res);
}
}
(2)使用 throw 抛出异常
除了系统自动抛出异常外,在编程过程中,有些问题是系统无法自动发现并解决的,如年龄不在正常范围之内,性别输入的不是“男”或“女”等,此时需要程序员而不是系统来自行抛出异常,把问题提交给调用者去解决。在 Java 语言中,可以使用 throw 关键字来自行抛出异常。
throw new Exception("message")
使用
throw语句抛出异常,让调用者解决异常。
public void divide(int dividend, int divisor) {
if (divisor == 0) {
throw new ArithmeticException("Cannot divide by zero");
} else {
int result = dividend / divisor;
System.out.println("Result: " + result);
}
}
public static void main(String[] args) {
try {
int dividend = 10;
int divisor = 0;
divide(dividend, divisor);
} catch (ArithmeticException e) {
System.err.println("Exception caught: " + e.getMessage());
}
}
Exception caught: Cannot divide by zero
- 如果 throw 语句抛出的异常是 Checked 异常,则该 throw 语句要么处于 try 块⾥,显式捕获该异常,要么放在⼀个带 throws 声明抛出的⽅法中,即把该异常交给该⽅法的调⽤者处理;
- 如果 throw 语句抛出的异常是 Runtime 异常,则该语句⽆须放在 try 块⾥,也⽆须放在带 throws 声明抛出的⽅法中;程序既可以显式使⽤ try…catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给该⽅法调⽤者处理。
自行抛出Runtime 异常比自行抛出Checked 异常的灵活性更好。同样,抛出 Checked 异常则可以让编译器提醒程序员必须处理该异常。
throw 和 throws 区别
- 作⽤不同:throw⽤于程序员⾃⾏产⽣并抛出异常,throws⽤于声明该⽅法内抛出了异常。
- 使⽤位置不同:throw位于⽅法体内部,可以作为单独的语句使⽤;throws必须跟在⽅法参数列表的后⾯,不能单独使⽤。
- 内容不同:throw抛出⼀个异常对象,只能是⼀个;throws后⾯跟异常类,可以跟多个。
1.2.4 自定义异常
在 Java 中,我们可以通过定义自己的异常类来表示特定的错误情况和处理方式。自定义异常类通常包含以下内容:
- 定义异常类,并继承
Exception或者RunTimeException。 - 编写异常类的构造方法,向父类构造方法传入异常描述信息,并继承父类的其他实现方法。
- 实例化自定义异常对象,并在程序中使用
throw抛出。
public class MyException extends Exception {
private String message;
public MyException(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
}
public void myMethod(int arg) throws MyException {
if (arg < 0 || arg > 100) {
throw new MyException("Argument out of range");
} else {
// 执行正常的代码逻辑
}
}
使用自定义异常类可以为我们提供更细粒度的错误处理方式,并使代码具有更好的可读性和可维护性。
自定义异常可能是编译时异常,也可以是运行时异常。
- 如果自定义异常类继承
Excpetion,则是编译时异常。
特点:方法中抛出的是编译时异常,必须在方法上使用throws声明,强制调用者处理。- 如果自定义异常类继承
RuntimeException,则运行时异常。
特点:方法中抛出的是运行时异常,不需要在方法上用throws声明。
1.2.5 异常链
在 Java 中,异常链(Exception Chaining)是指将一个异常作为另一个异常的原因而被抛出。通过异常链,我们可以更清晰地了解异常的触发原因,并且可以在捕获异常时获取到完整的异常信息。
在创建异常链时,可以使用以下两种方式之一:
-
使用带有
cause参数的异常构造方法:异常类的构造方法中通常会包含一个带有cause参数的重载版本,用于指定异常的原因。public MyException(String message, Throwable cause) { super(message, cause); }在这种情况下,可以将一个异常对象作为
cause参数传递给当前异常的构造方法。 -
使用
initCause方法:Throwable类提供了initCause方法,用于将异常对象设置为另一个异常的原因。try { // ... } catch (Exception e) { MyException ex = new MyException("Custom exception"); ex.initCause(e); throw ex; }在这种情况下,我们首先创建一个新的异常对象,然后使用
initCause方法将之前捕获的异常对象设置为其原因。
public class TryDemoFive {
public static void main(String[] args) {
try {
testThree();
} catch (Exception e) {
// e.printStackTrace();
System.out.printf( e.getMessage());
}
}
public static void testOne() throws Exception {
throw new Exception("第一个异常");
}
public static void testTwo() throws Exception {
try {
testOne();
} catch (Exception e) {
System.out.println(e.getCause());
throw new Exception("我是新产生的异常1", e);
}
}
public static void testThree() throws Exception {
try {
testTwo();
} catch (Exception e) {
System.out.println(e.getCause());
Exception exception = new Exception("新产生的异常2");
exception.initCause(e);
throw exception;
}
}
}
null
java.lang.Exception: 第一个异常
新产生的异常2
通过使用异常链,可以在异常处理过程中保留和传递更多的信息,使开发人员能够更好地理解异常的来源和根本原因。



















