

大家好!今天我们来学习《Java 程序设计》中的第 12 章 —— 异常处理。在编程过程中,错误和异常是不可避免的。一个健壮的程序必须能够妥善处理各种异常情况。本章将详细介绍 Java 中的异常处理机制,帮助大家编写出更稳定、更可靠的 Java 程序。

在 Java 中,异常(Exception) 是指程序在运行过程中发生的非正常事件,它会中断程序的正常执行流程。
想象一下现实生活中的场景:当你开车去上班时,可能会遇到轮胎漏气、发动机故障等意外情况,这些情况会阻止你按计划到达公司。在程序中也是如此,比如:
这些情况都可以称为异常。Java 的异常处理机制提供了一种优雅的方式来处理这些意外情况,使程序能够继续运行或友好地终止。
Java 中的所有异常都是通过类来表示的,这些类统称为异常类。Java 提供了一个完善的异常类体系,所有异常类都直接或间接继承自Throwable类。
Throwable类有两个重要的子类:
Exception类又可以分为:
IOException、SQLException等。NullPointerException、ArrayIndexOutOfBoundsException等。下面是异常类的继承关系图:
@startuml
title Java异常类体系
Throwable <|-- Error
Throwable <|-- Exception
Exception <|-- RuntimeException
Exception <|-- IOException
Exception <|-- SQLException
RuntimeException <|-- NullPointerException
RuntimeException <|-- ArrayIndexOutOfBoundsException
RuntimeException <|-- ArithmeticException
@enduml
Java 提供了一套完整的异常处理机制,主要通过try、catch、finally、throw和throws关键字来实现。
异常处理的核心思想是抛出异常和捕获异常:
形象地说,这就像生活中 "上报问题" 和 "解决问题" 的过程:员工遇到无法解决的问题(抛出异常),上报给经理(捕获异常),经理来处理这个问题。
try-catch-finally是 Java 中处理异常的基本结构,语法如下:
try {
// 可能会发生异常的代码
} catch (异常类型1 异常对象名) {
// 处理异常类型1的代码
} catch (异常类型2 异常对象名) {
// 处理异常类型2的代码
} finally {
// 无论是否发生异常,都会执行的代码
}执行流程示意图:

示例代码:
public class TryCatchFinallyDemo {
public static void main(String[] args) {
int a = 10;
int b = 0;
int[] arr = {1, 2, 3};
try {
// 可能发生异常的代码
int result = a / b; // 会抛出ArithmeticException
System.out.println("数组的第4个元素是:" + arr[3]); // 会抛出ArrayIndexOutOfBoundsException
System.out.println("计算结果:" + result);
} catch (ArithmeticException e) {
// 处理算术异常
System.out.println("发生算术异常:" + e.getMessage());
e.printStackTrace(); // 打印异常堆栈信息
} catch (ArrayIndexOutOfBoundsException e) {
// 处理数组下标越界异常
System.out.println("发生数组下标越界异常:" + e.getMessage());
} finally {
// 无论是否发生异常,都会执行
System.out.println("finally块执行了,通常用于释放资源");
}
System.out.println("程序继续执行...");
}
}运行上述代码,输出结果:

Java 7 及以上版本允许在一个catch块中捕获多种类型的异常,使用|分隔不同的异常类型。
示例代码:
import java.io.FileNotFoundException;
import java.io.IOException;
public class MultiCatchDemo {
public static void main(String[] args) {
try {
// 模拟可能抛出不同异常的操作
int choice = Integer.parseInt(args[0]);
if (choice == 1) {
throw new FileNotFoundException("文件未找到");
} else if (choice == 2) {
throw new IOException("I/O操作失败");
} else if (choice == 3) {
throw new ArithmeticException("算术错误");
}
} catch (FileNotFoundException e) {
// 先捕获子类异常
System.out.println("处理文件未找到异常:" + e.getMessage());
} catch (IOException e) {
// 再捕获父类异常
System.out.println("处理其他I/O异常:" + e.getMessage());
} catch (ArithmeticException e) {
// 捕获ArithmeticException
System.out.println("处理算术异常:" + e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
// 捕获数组下标越界异常(当没有传入命令行参数时)
System.out.println("请传入一个整数参数(1-3)");
}
System.out.println("程序结束");
}
}
说明:
catch块捕获多种异常类型时,这些异常类型不能有继承关系。catch块更简洁,尤其是当多种异常的处理逻辑相同时。java MultiCatchDemo 1:测试 FileNotFoundExceptionjava MultiCatchDemo 2:测试 IOExceptionjava MultiCatchDemo 3:测试 ArithmeticExceptionjava MultiCatchDemo:测试 ArrayIndexOutOfBoundsException 如果一个方法可能会抛出异常,但不想在方法内部处理,而是让调用者来处理,可以使用throws关键字在方法声明处声明该方法可能抛出的异常。
语法:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1, 异常类型2, ... {
// 方法体
}示例代码:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ThrowsDemo {
// 声明方法可能抛出FileNotFoundException和IOException
public static void readFile(String fileName) throws FileNotFoundException, IOException {
FileInputStream fis = new FileInputStream(fileName);
int data = fis.read();
while (data != -1) {
System.out.print((char) data);
data = fis.read();
}
fis.close();
}
public static void main(String[] args) {
try {
// 调用声明了抛出异常的方法,必须处理这些异常
readFile("test.txt");
} catch (FileNotFoundException e) {
System.out.println("文件未找到:" + e.getMessage());
} catch (IOException e) {
System.out.println("文件读取错误:" + e.getMessage());
}
System.out.println("\n程序执行完毕");
}
}
说明:
throws声明。throws声明,编译器不会强制要求。try-catch处理这些异常,要么在当前方法中也用throws声明继续向上抛出。 throw语句用于手动抛出一个具体的异常对象。通常在满足特定条件时,我们认为这是一个异常情况,就可以手动抛出异常。
语法:
throw 异常对象;示例代码:
public class ThrowDemo {
// 计算年龄的方法,如果年龄不合法则抛出异常
public static void printAge(int birthYear) {
int currentYear = 2023;
int age = currentYear - birthYear;
if (birthYear < 1900 || birthYear > currentYear) {
// 手动抛出异常
throw new IllegalArgumentException("出生年份不合法:" + birthYear);
}
System.out.println("今年" + age + "岁");
}
public static void main(String[] args) {
try {
printAge(2000); // 合法的出生年份
printAge(2050); // 不合法的出生年份,会抛出异常
printAge(1850); // 这行代码不会执行
} catch (IllegalArgumentException e) {
System.out.println("捕获到异常:" + e.getMessage());
}
System.out.println("程序继续执行");
}
}运行结果:

throw与throws的区别:
throw用于方法内部,抛出的是一个具体的异常对象。throws用于方法声明处,声明的是方法可能抛出的异常类型,可以是多个。 Java 7 引入了try-with-resources语句,用于自动管理资源(如文件流、数据库连接等)。它确保在资源使用完毕后自动关闭资源,无需在finally块中手动关闭。
要使用try-with-resources,资源类必须实现AutoCloseable接口(或其子类Closeable接口)。
语法:
try (资源声明) {
// 使用资源的代码
} catch (异常类型 异常对象名) {
// 处理异常的代码
}示例代码:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class TryWithResourcesDemo {
public static void main(String[] args) {
// try-with-resources语句,资源会自动关闭
try (FileInputStream fis = new FileInputStream("test.txt")) {
int data = fis.read();
while (data != -1) {
System.out.print((char) data);
data = fis.read();
}
} catch (FileNotFoundException e) {
System.out.println("文件未找到:" + e.getMessage());
} catch (IOException e) {
System.out.println("文件读取错误:" + e.getMessage());
}
System.out.println("\n程序执行完毕");
}
}
传统方式与 try-with-resources 对比:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
// 传统方式需要在finally中手动关闭资源
public class TraditionalResourceHandling {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("test.txt");
// 使用资源,这里添加一些实际的读取操作示例
int data = fis.read();
while (data != -1) {
System.out.print((char) data);
data = fis.read();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close(); // 关闭资源可能也会抛出异常
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
说明:
try-with-resources语句可以声明多个资源,用分号分隔。try-with-resources语句也可以有catch和finally块,用于处理异常或执行必要的清理工作。 Java 提供的异常类可能无法满足所有业务需求,这时我们可以自定义异常类。自定义异常类通常继承自Exception(checked exception)或RuntimeException(unchecked exception)。
自定义异常类的步骤:
Exception或RuntimeException。示例代码:
// 自定义异常类:用户年龄不合法异常
class InvalidAgeException extends Exception {
// 无参构造方法
public InvalidAgeException() {
super();
}
// 带有详细信息的构造方法
public InvalidAgeException(String message) {
super(message);
}
}
// 自定义异常类:用户姓名为空异常(继承自RuntimeException)
class EmptyNameException extends RuntimeException {
public EmptyNameException() {
super();
}
public EmptyNameException(String message) {
super(message);
}
}
// 使用自定义异常的示例
public class CustomExceptionDemo {
// 注册用户的方法
public static void registerUser(String name, int age) throws InvalidAgeException {
if (name == null || name.trim().isEmpty()) {
// 抛出unchecked异常,不需要在方法声明中throws
throw new EmptyNameException("用户名不能为空");
}
if (age < 0 || age > 150) {
// 抛出checked异常,需要在方法声明中throws
throw new InvalidAgeException("年龄不合法:" + age + ",年龄必须在0-150之间");
}
System.out.println("用户注册成功:" + name + "," + age + "岁");
}
public static void main(String[] args) {
try {
registerUser("张三", 25); // 正常情况
registerUser("", 30); // 姓名为空,会抛出EmptyNameException
registerUser("李四", 200); // 年龄不合法,会抛出InvalidAgeException
} catch (InvalidAgeException e) {
System.out.println("注册失败:" + e.getMessage());
} catch (EmptyNameException e) {
System.out.println("注册失败:" + e.getMessage());
}
System.out.println("程序结束");
}
}运行结果:

何时需要自定义异常:
断言(Assertion)是 Java 1.4 引入的特性,用于在程序开发和测试阶段检查某些条件是否满足。如果断言失败,会抛出AssertionError。
断言的语法有两种形式:
assert 布尔表达式;如果布尔表达式的值为false,则抛出AssertionError。
2.带消息的形式:
assert 布尔表达式 : 消息表达式;如果布尔表达式的值为false,则抛出AssertionError,并将消息表达式的值作为错误消息。
默认情况下,Java 虚拟机(JVM)是关闭断言功能的。要开启断言,需要使用-ea(或-enableassertions)参数。
开启断言的方式:
java -ea 类名java -ea:包名... 类名java -ea:类名 类名关闭断言的方式:
-da(或-disableassertions)参数,用法与-ea类似。在 IDE(如 Eclipse、IntelliJ IDEA)中,可以在运行配置中设置 VM 参数来开启或关闭断言。
断言主要用于:
注意:
public class AssertionDemo {
// 计算三角形面积的方法(海伦公式)
public static double calculateTriangleArea(double a, double b, double c) {
// 检查前置条件:三角形的三条边必须为正数
assert a > 0 && b > 0 && c > 0 : "三角形的边长必须为正数";
// 检查前置条件:三角形任意两边之和大于第三边
assert a + b > c && a + c > b && b + c > a : "不满足三角形两边之和大于第三边";
double s = (a + b + c) / 2;
double area = Math.sqrt(s * (s - a) * (s - b) * (s - c));
// 检查后置条件:面积必须为正数
assert area > 0 : "计算出的面积必须为正数";
return area;
}
public static void main(String[] args) {
try {
double area1 = calculateTriangleArea(3, 4, 5);
System.out.println("直角三角形的面积:" + area1);
// 测试无效的三角形(两边之和不大于第三边)
double area2 = calculateTriangleArea(1, 1, 3);
System.out.println("面积:" + area2);
} catch (AssertionError e) {
System.out.println("断言失败:" + e.getMessage());
}
}
}
运行说明:
java -ea AssertionDemo),第二个计算会触发断言失败,输出:直角三角形的面积:6.0
断言失败:不满足三角形两边之和大于第三边本章我们学习了 Java 中的异常处理机制,主要内容包括:
Throwable,主要分为Error和Exception两大类。Exception又分为 checked exception 和 unchecked exception。try-catch-finally语句捕获和处理异常。throws声明方法可能抛出的异常。throw手动抛出异常。try-with-resources自动管理资源。掌握异常处理是编写健壮 Java 程序的关键。合理地使用异常处理机制,可以使程序在遇到错误时能够优雅地处理,而不是直接崩溃,同时也便于调试和维护。
练习 1 参考答案:
import java.util.InputMismatchException;
import java.util.Scanner;
public class DivisionCalculator {
public static void main(String[] args) {
Scanner scanner = null;
try {
scanner = new Scanner(System.in);
System.out.print("请输入被除数:");
int dividend = scanner.nextInt();
System.out.print("请输入除数:");
int divisor = scanner.nextInt();
int result = dividend / divisor;
System.out.println(dividend + " / " + divisor + " = " + result);
} catch (ArithmeticException e) {
System.out.println("错误:除数不能为0");
} catch (InputMismatchException e) {
System.out.println("错误:请输入有效的整数");
} finally {
if (scanner != null) {
scanner.close();
System.out.println("资源已释放");
}
}
System.out.println("程序结束");
}
}

希望本章的内容能帮助你理解和掌握 Java 异常处理的相关知识。如果有任何疑问或建议,欢迎在评论区留言讨论!