Java基础之异常
一、异常概述
1.1 什么是异常?
异常是程序在编译阶段或运行阶段出现的非正常、不符合预期的情况。Java将所有程序错误、异常情况封装成异常对象,统一管理和处理。
1.2 异常的作用
- 避免程序直接崩溃终止,提升程序健壮性;
- 精准记录报错信息,方便开发者快速定位、修复Bug;
- 统一错误处理机制,让程序容错、可控。
1.3 异常示例
数组下标越界异常,是最基础的运行时异常案例:
package com.lemon.test;
public class test1 {
public static void main(String[] args) {
// 数组长度为3,合法下标:0、1、2
int[] nums = {1,2,3};
// 访问下标10,超出数组长度,触发异常
System.out.println(nums[10]);
}
}
终端显示异常信息:

报错结果说明:程序抛出 ArrayIndexOutOfBoundsException 数组下标越界异常,精准定位到报错代码行,程序直接终止运行。
二、异常整体体系结构
Java所有错误和异常的顶层父类是 Throwable,分为两大分支:Error(系统错误)、Exception(程序异常)。

2.1 Error 系统级错误
核心特点:JVM底层、系统层面的致命问题,代码无法捕获、无法修复,一旦出现程序直接崩溃。
常见类型:
- VirtualMachineError:虚拟机错误(内存溢出、虚拟机崩溃)
- LinkageError:类链接错误(依赖类缺失、版本不匹配)
- AWTError:图形界面系统级错误
2.2 Exception 程序级异常
代码逻辑或外部资源问题导致的异常,可捕获、可处理,处理后程序可正常运行。分为两大类:运行时异常、受检查异常。
2.2.1 运行时异常(RuntimeException)
特点:编译阶段不报错,运行阶段才触发,编译器不强制处理,多由代码逻辑不严谨导致。
常见运行时异常:
- ArrayIndexOutOfBoundsException:数组下标越界
- NullPointerException:空指针异常(访问null对象的属性/方法)
- ClassCastException:类型强制转换异常
- ArithmeticException:算术异常(如整数除以0)
- IndexOutOfBoundsException:集合/数组下标越界父类异常
2.2.2 受检查异常(编译异常)
特点:编译阶段强制校验,必须手动处理(捕获/抛出),否则代码无法编译。多由外部资源、文件、网络、解析操作导致。
常见受检查异常:
- IOException:IO流通用异常
- FileNotFoundException:文件未找到异常
- EOFException:文件读取结束异常
- DateTimeParseException:时间格式解析异常
- ParseException:通用格式解析异常
2.3 异常体系核心总结
- Throwable:所有错误、异常的顶层父类;
- Error:系统致命错误,不可处理;
- RuntimeException:代码逻辑问题,编译不校验,可选处理;
- 受检查异常:外部资源问题,编译强制校验,必须处理。
三、Java异常处理四大机制
Java异常处理的核心在于四个关键字:throw、throws、try-catch、finally。它们共同构建了Java程序的健壮性。
1. throws - 声明异常
作用:在方法签名中声明该方法可能抛出的检查型异常(Checked Exception),提示调用者需要处理这些异常。
特点:仅声明异常类型,不进行具体处理。若异常最终无人捕获,将由JVM处理并终止程序。
语法:在方法参数列表后添加 throws 异常类型1, 异常类型2。
import java.io.FileNotFoundException;
import java.io.FileReader;
public class FileProcessor {
// 声明方法可能抛出FileNotFoundException
public static void openFile() throws FileNotFoundException {
FileReader reader = new FileReader("data.txt"); // 可能引发文件不存在异常
}
public static void main(String[] args) throws FileNotFoundException {
openFile(); // 调用者需处理或继续声明
}
}
2. throw - 主动抛出异常
作用:在方法内部主动创建并抛出异常对象,常用于业务规则校验或强制中断执行流程。
特点:可抛出任意 Throwable 子类(包括自定义异常),用于主动控制异常流程。
public class Validator {
public static void validateAge(int age) {
if (age < 0 || age > 120) {
// 手动抛出运行时异常(非检查型)
throw new IllegalArgumentException("年龄必须在0-120之间");
}
System.out.println("年龄校验通过");
}
public static void main(String[] args) {
validateAge(-5); // 触发异常
}
}
3. try-catch - 捕获并处理异常
作用:主动捕获代码块中的异常,防止程序意外终止,是实际开发中最常用的处理方式。
结构:
try:包裹可能引发异常的代码catch:捕获特定类型异常并处理- 可多级捕获:
catch (ExceptionType1 e) {...} catch (ExceptionType2 e) {...}
public class ArrayHandler {
public static void main(String[] args) {
int[] data = {10, 20, 30};
try {
System.out.println(data[5]); // 可能越界
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("错误:数组索引越界");
e.printStackTrace(); // 打印堆栈跟踪(调试用)
}
System.out.println("后续代码正常执行");
}
}
4. finally - 资源清理块
核心特性:无论是否发生异常或存在return语句,finally块中的代码必定执行(除非调用 System.exit())。
主要用途:释放资源(如文件句柄、数据库连接、网络套接字),避免资源泄漏。
import java.io.FileReader;
import java.io.IOException;
public class ResourceManager {
public static void main(String[] args) {
FileReader reader = null;
try {
reader = new FileReader("config.txt");
// 文件操作...
} catch (IOException e) {
e.printStackTrace();
} finally {
// 确保资源关闭
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
关键:
finally执行优先级高于try/catch中的return- Java 7+ 推荐使用
try-with-resources自动关闭资源(实现AutoCloseable接口):try (FileReader reader = new FileReader("file.txt")) { // 自动关闭资源 } catch (IOException e) { e.printStackTrace(); }
finally不执行的原因:
- try 块内执行 System.exit()
在 try 代码块中调用 System.exit(整数) 会直接终止整个 JVM,程序立刻退出,根本不会跳转执行 finally 块。 - try 块陷入死循环、死锁、永久阻塞
try 里写了无终止条件的 while(true) 死循环、线程死锁、无限等待网络 / 锁资源,程序一直卡死在 try 内部,永远走不到 finally。 - 外部强制杀死进程、断电、系统崩溃
手动在任务管理器 / 命令行杀掉 Java 进程、电脑突然断电、系统蓝屏崩溃,JVM 被粗暴终止,没有机会执行 finally 收尾。 - 调用废弃方法 Thread.stop() 暴力终止线程
用 thread.stop() 强行杀死线程,这种暴力终止会直接中断线程执行,跳过 finally 不执行。 - 发生严重系统级 Error
出现 StackOverflowError 栈溢出、OutOfMemoryError 内存溢出等严重错误,属于 JVM 级致命故障,程序直接崩溃,finally 无法执行。
四、JDK7 & JDK9 异常处理新特性
传统 try-catch-finally 存在代码冗余、资源关闭繁琐等问题。JDK7 引入了三大优化特性,JDK9 进一步升级,显著提升代码简洁性与安全性。
特性一:多异常同捕 (Multi-catch)
核心:合并相同处理逻辑的异常捕获,消除冗余 catch 块。
规则:
- 异常类型用
|分隔,如:catch (IOException | SQLException e); - 异常类型不能有继承关系(如禁止同时捕获
Exception和其子类); - 异常对象
e自动设置为常量(final),不可重新赋值; - 仅适用于统一处理逻辑,差异处理仍需单独
catch。
示例:
try {
Files.readAllBytes(Paths.get("file.txt")); // 可能抛出 IOException
int x = 1 / 0; // 可能抛出 ArithmeticException
} catch (IOException | ArithmeticException e) {
System.out.println("文件或运算错误");
e.printStackTrace();
}
特性二:自动资源关闭 (try-with-resources)
核心价值:彻底替代 finally 手动关闭资源,杜绝资源泄漏。
基础用法(JDK7):
资源在 try() 中创建,作用域限于 try 块,执行完毕自动关闭:
try (FileReader reader = new FileReader("test.txt")) {
// 使用资源
} catch (IOException e) {
e.printStackTrace();
}
多资源管理(JDK7):
支持分号分隔多个资源,按创建逆序自动关闭:
try (FileInputStream in = new FileInputStream("in.txt");
FileOutputStream out = new FileOutputStream("out.txt")) {
// 资源操作
} // 先关闭 out,再关闭 in
JDK9 升级:
支持直接使用外部已初始化的资源变量:
FileReader reader = new FileReader("test.txt");
try (reader) { // 直接引用外部变量
// 使用资源
} // 仍自动关闭!
优势总结:
- 代码极简,消除
finally嵌套; - 自动关闭,避免遗漏或空指针;
- 多资源逆序关闭,安全性更高;
- JDK9 支持外部变量,灵活适配业务。
特性三:精准重抛异常 (Precise Rethrow)
核心价值:编译器智能识别 catch 块中实际抛出的异常类型,简化 throws 声明。
示例对比:
// JDK7 前:需声明宽泛异常
public void read() throws Exception {
try {
Files.readString(Path.of("file.txt"));
} catch (Exception e) {
throw e; // 编译器要求 throws Exception
}
}
// JDK7+:仅声明实际异常类型
public void read() throws IOException {
try {
Files.readString(Path.of("file.txt"));
} catch (Exception e) {
throw e; // 编译器识别实际抛出 IOException
}
}
要点
- 多异常同捕:竖线分隔、禁父子类共存、异常对象常量化;
- 自动资源关闭:
- JDK7:
try()内创建资源,多资源逆序关闭; - JDK9:支持外部资源变量引用;
- JDK7:
- 精准重抛:编译器动态分析异常类型,避免冗余
throws声明。
这些特性显著提升代码可读性、健壮性和开发效率,是 Java 异常处理的里程碑式改进。
五、自定义业务异常
JDK 自带异常(如 NullPointerException)无法覆盖所有业务场景(如余额不足、用户不存在),因此需自定义异常。这有助于统一错误处理、区分系统异常(如数据库错误)和业务异常(如密码错误),便于全局异常管理和前端提示。
5.1 自定义运行时异常
自定义异常通常继承 RuntimeException(运行时异常),避免强制处理。示例:
// 自定义业务异常类
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message); // 传递错误信息
}
}
优势:无需在方法签名声明,减少代码冗余。
5.2 业务场景使用
在业务逻辑中主动抛出自定义异常,统一处理。示例:
public class AccountService {
// 余额校验业务逻辑
public static void checkBalance(double balance) {
if (balance < 10) {
throw new BusinessException("余额不足,最低充值10元!"); // 手动抛出
}
}
public static void main(String[] args) {
try {
checkBalance(5); // 触发异常
} catch (BusinessException e) {
System.out.println("业务错误: " + e.getMessage()); // 统一捕获处理
}
}
}
企业实践:在全局异常处理器中捕获 BusinessException,返回标准错误响应,提升可维护性。
六、异常开发规范
遵循规范可提升代码健壮性:
-
运行时异常处理:优先通过代码逻辑规避(如判空、下标校验),避免过度使用 try-catch。例如:
if (str != null) { // 判空规避 NullPointerException str.length(); } -
受检查异常处理:必须处理(编译强制校验)。优先使用 try-catch 内部处理,少用 throws 上抛。例如:
try { Files.readFile(path); // 受检查异常 } catch (IOException e) { log.error("文件读取失败", e); // 内部处理 } -
禁止空 catch:捕获异常后必须写处理逻辑(如日志、恢复),禁止空块隐藏错误:
// 错误示例:空 catch try { /* code */ } catch (Exception e) { } // 正确示例 try { /* code */ } catch (Exception e) { log.error("异常详情", e); // 必须处理 } -
资源释放:IO、数据库等资源优先使用 try-with-resources,自动关闭:
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) { // 自动关闭资源 } catch (IOException e) { // 处理 } -
业务异常自定义:统一使用自定义异常(如
BusinessException),便于全局管理和前端交互。避免混用 JDK 异常。
七、总结
Java 异常处理是面试和实战核心,总结关键点:
-
异常体系:继承自
Throwable,分Error(不可处理,如内存溢出)和Exception(可处理)。Exception分运行时异常(编译不校验,如NullPointerException)和受检查异常(编译强制校验,如IOException)。
-
四大处理机制:
throws:声明异常。throw:手动抛出异常。try-catch:捕获并处理异常。finally:确保资源释放(JDK7+ 使用 try-with-resources 优化)。
-
新特性:
- JDK7:多异常捕获(
catch (Ex1 | Ex2 e))、try-with-resources。 - JDK9:外部资源变量引用改进(如
try (resource)支持 final 变量)。
- JDK7:多异常捕获(
-
企业开发:
- 自定义业务异常实现统一错误管理。
- 遵循规范提升项目健壮性、可维护性和用户体验。
核心价值:异常处理不仅是语法,更是工程实践。掌握这些,能高效应对面试和实际开发挑战。
更多推荐


所有评论(0)