跳到主要内容 Java 异常处理:从原理到实战最佳实践 | 极客日志
Java java
Java 异常处理:从原理到实战最佳实践 Java 异常体系包含 Error、Checked Exception 和 Unchecked Exception。核心语法涉及 try-catch-finally、throws 和 throw。try-with-resources 用于自动资源管理。自定义异常需继承 Exception 或 RuntimeException。最佳实践包括捕获具体异常、不忽略异常、释放资源及提供有意义信息。实际项目中应搭建统一异常处理框架,封装错误码与返回结果,区分业务与系统异常以提升代码健壮性与用户体验。
Java 异常处理:从原理到实战最佳实践
1.1 本章学习目标与重点
掌握异常的分类与核心概念,理解异常处理的设计思想。
熟练运用 try-catch-finally、throws、throw 处理异常。
掌握自定义异常的编写与使用场景,规范异常处理流程。
本章重点是 异常处理的最佳实践 和 避免常见误区,这是提升代码健壮性的核心技能。
1.2 异常的核心概念与分类
1.2.1 什么是异常 异常是指程序运行过程中出现的非正常情况,它会中断程序的正常执行流程。比如文件找不到、数组下标越界、空指针访问等,这些情况都会触发异常。Java 中所有异常都是 Throwable 类的子类,异常处理的本质是捕获并处理这些非正常情况,保证程序可以继续运行或优雅退出。
1.2.2 异常的分类 Java 中的异常体系分为三大类,它们的父类都是 Throwable:
Error(错误)
是 JVM 内部的严重错误,属于不可恢复的异常,程序无法处理。
常见的有 OutOfMemoryError(内存溢出)、StackOverflowError(栈溢出)。
示例:递归调用没有终止条件,会导致栈溢出
Checked Exception(受检异常)
也称编译时异常,编译器会强制要求程序处理这类异常。
常见的有 IOException(IO 异常)、SQLException(数据库异常)。
处理方式:要么用 try-catch 捕获,要么用 throws 声明抛出。
Unchecked Exception(非受检异常)
也称运行时异常,继承自 RuntimeException,编译器不强制处理。
常见的有 NullPointerException(空指针)、ArrayIndexOutOfBoundsException(数组下标越界)、ClassCastException(类型转换异常)。
这类异常通常是程序逻辑错误导致的,建议通过代码规范来避免,而不是捕获处理。
public class ErrorDemo {
public static void recursion () {
recursion();
}
public static void main (String[] args) {
recursion();
}
}
核心结论:异常体系的继承关系为 Throwable → Error / Exception → RuntimeException。
1.2.3 常见异常及其产生原因 异常类型 产生原因 NullPointerException 调用了 null 对象的方法或访问了 null 对象的属性 ArrayIndexOutOfBoundsException 数组下标超出了数组的长度范围 ClassCastException 强制类型转换失败,比如将 String 转为 Integer ArithmeticException 算术运算错误,比如整数除以 0 IOException 文件读写失败,比如文件不存在或权限不足
1.3 异常处理的核心语法
1.3.1 try-catch:捕获并处理异常 try-catch 是最基础的异常处理结构,try 块包裹可能抛出异常的代码,catch 块捕获并处理对应的异常。
基本语法 try {
} catch (异常类型 1 变量名 1 ) {
} catch (异常类型 2 变量名 2 ) {
}
代码实操:捕获单个异常 以空指针异常为例,演示 try-catch 的使用:
public class TryCatchSingleDemo {
public static void main (String[] args) {
String str = null ;
try {
System.out.println(str.length());
} catch (NullPointerException e) {
System.out.println("发生空指针异常:" + e.getMessage());
e.printStackTrace();
}
System.out.println("程序继续运行..." );
}
}
发生空指针异常:null java.lang.NullPointerException: Cannot invoke "String.length()" because "str" is null at TryCatchSingleDemo.main(TryCatchSingleDemo.java:6 ) 程序继续运行...
代码实操:捕获多个异常 当一段代码可能抛出多种异常时,可以使用多个 catch 块,注意异常类型要从小到大排列(子类在前,父类在后):
public class TryCatchMultiDemo {
public static void main (String[] args) {
int [] arr = {1 , 2 , 3 };
try {
System.out.println(arr[5 ]);
String str = null ;
System.out.println(str.length());
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组下标越界:" + e.getMessage());
} catch (NullPointerException e) {
System.out.println("空指针异常:" + e.getMessage());
} catch (Exception e) {
System.out.println("发生未知异常:" + e.getMessage());
}
}
}
注意事项:如果父类异常写在子类异常前面,会导致子类异常的 catch 块永远无法执行,编译器会报错。
1.3.2 finally:无论是否异常都会执行 finally 块用于执行必须要完成的操作,比如关闭流、释放资源等。无论 try 块是否抛出异常,finally 块都会执行。
基本语法 try {
} catch (异常类型 变量名) {
} finally {
}
代码实操:finally 释放资源 以文件读取为例,演示 finally 关闭流资源:
import java.io.FileInputStream;
import java.io.IOException;
public class FinallyDemo {
public static void main (String[] args) {
FileInputStream fis = null ;
try {
fis = new FileInputStream ("test.txt" );
int data = fis.read();
System.out.println("读取到数据:" + data);
} catch (IOException e) {
System.out.println("文件读取异常:" + e.getMessage());
} finally {
if (fis != null ) {
try {
fis.close();
System.out.println("流资源已关闭" );
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
文件读取异常:test.txt (系统找不到指定的文件。)
核心结论:finally 块的唯一例外是 System.exit(0),它会直接终止 JVM,finally 块不会执行。
1.3.3 throws:声明抛出异常 throws 用于在方法声明上声明该方法可能抛出的异常,将异常的处理责任交给调用者。它适用于不想在当前方法处理异常,或者无法处理异常的场景。
基本语法 public 返回值类型 方法名 (参数列表) throws 异常类型 1 , 异常类型 2 {
}
代码实操:throws 声明异常 定义一个读取文件的方法,声明抛出 IOException,由调用者处理异常:
import java.io.FileInputStream;
import java.io.IOException;
public class ThrowsDemo {
public static void readFile () throws IOException {
FileInputStream fis = new FileInputStream ("test.txt" );
int data = fis.read();
System.out.println("读取到数据:" + data);
fis.close();
}
public static void main (String[] args) {
try {
readFile();
} catch (IOException e) {
System.out.println("处理文件读取异常:" + e.getMessage());
}
}
}
非受检异常(RuntimeException)可以不用 throws 声明,编译器不会强制要求。
子类重写父类方法时,抛出的异常不能超过父类方法声明的异常范围(子类异常 ≤ 父类异常)。
1.3.4 throw:主动抛出异常 throw 用于在方法内部主动抛出一个异常对象,通常用于满足特定业务条件时触发异常。
基本语法 throw new 异常类型("异常描述信息" );
代码实操:throw 主动抛异常 模拟用户登录场景,当用户名或密码错误时,主动抛出异常:
public class ThrowDemo {
public static void login (String username, String password) {
if (!"admin" .equals(username)) {
throw new RuntimeException ("用户名错误" );
}
if (!"123456" .equals(password)) {
throw new RuntimeException ("密码错误" );
}
System.out.println("登录成功!" );
}
public static void main (String[] args) {
try {
login("admin" , "123" );
} catch (RuntimeException e) {
System.out.println("登录失败:" + e.getMessage());
}
}
}
核心结论:throws 是方法层面的异常声明,throw 是代码层面的异常抛出,两者经常配合使用。
1.4 异常处理的进阶特性
1.4.1 try-with-resources:自动释放资源 JDK 7 引入的 try-with-resources 语法,可以自动关闭实现了 AutoCloseable 接口的资源,无需手动在 finally 块中关闭。常见的资源如 FileInputStream、BufferedReader、Connection 等都实现了该接口。
基本语法 try (资源声明语句) {
} catch (异常类型 变量名) {
}
代码实操:自动关闭文件流 对比传统 try-catch-finally 和 try-with-resources 的写法:
import java.io.FileInputStream;
import java.io.IOException;
public class TryWithResourcesDemo {
public static void main (String[] args) {
FileInputStream fis = null ;
try {
fis = new FileInputStream ("test.txt" );
System.out.println("读取数据:" + fis.read());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null ) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
try (FileInputStream fis2 = new FileInputStream ("test.txt" )) {
System.out.println("读取数据:" + fis2.read());
} catch (IOException e) {
e.printStackTrace();
}
}
}
核心结论:try-with-resources 语法更简洁,代码可读性更高,是推荐的资源释放方式。
1.4.2 异常链:传递异常信息 异常链是指在捕获一个异常后,抛出另一个异常并将原异常作为原因传递,这样可以保留异常的完整调用链,便于调试。使用 initCause() 方法或异常构造方法传递原异常。
代码实操:异常链的使用 模拟业务层调用数据访问层,捕获数据访问层异常并包装为业务异常:
public class ExceptionChainDemo {
public static void queryData () throws SQLException {
throw new SQLException ("数据库连接失败" );
}
public static void processBusiness () {
try {
queryData();
} catch (SQLException e) {
RuntimeException businessException = new RuntimeException ("业务处理失败" );
businessException.initCause(e);
throw businessException;
}
}
public static void main (String[] args) {
try {
processBusiness();
} catch (RuntimeException e) {
System.out.println("异常信息:" + e.getMessage());
System.out.println("根因异常:" + e.getCause().getMessage());
e.printStackTrace();
}
}
}
异常信息:业务处理失败 根因异常:数据库连接失败 java.lang.RuntimeException : 业务处理失败 at ExceptionChainDemo.processBusiness (ExceptionChainDemo.java :12 ) at ExceptionChainDemo.main (ExceptionChainDemo.java :19 ) Caused by: java.sql.SQLException: 数据库连接失败 at ExceptionChainDemo.queryData (ExceptionChainDemo.java :4 ) at ExceptionChainDemo.processBusiness (ExceptionChainDemo.java :9 ) ... 1 more
1.5 自定义异常
1.5.1 为什么需要自定义异常 Java 提供的内置异常无法满足所有业务场景的需求,比如用户注册时的'用户名已存在'、'密码长度不足'等异常,需要自定义异常来描述。自定义异常的优势:
异常信息更贴合业务,便于开发者定位问题。
可以区分系统异常和业务异常,便于统一处理。
1.5.2 自定义异常的编写步骤
定义一个类,继承 Exception(受检异常)或 RuntimeException(非受检异常)。
提供无参构造方法和带异常信息的构造方法。
(可选)提供带异常信息和根因异常的构造方法。
1.5.3 代码实操 1:自定义受检异常 定义一个 UsernameExistException,表示用户名已存在异常:
public class UsernameExistException extends Exception {
public UsernameExistException () {
super ();
}
public UsernameExistException (String message) {
super (message);
}
public UsernameExistException (String message, Throwable cause) {
super (message, cause);
}
}
1.5.4 代码实操 2:自定义非受检异常 定义一个 PasswordLengthException,表示密码长度不足异常:
public class PasswordLengthException extends RuntimeException {
public PasswordLengthException () {
super ();
}
public PasswordLengthException (String message) {
super (message);
}
public PasswordLengthException (String message, Throwable cause) {
super (message, cause);
}
}
1.5.5 自定义异常的使用场景 import java.util.HashSet;
import java.util.Set;
public class CustomExceptionDemo {
private static final Set<String> EXIST_USERNAMES = new HashSet <>();
static {
EXIST_USERNAMES.add("admin" );
EXIST_USERNAMES.add("user1" );
}
public static void register (String username, String password) throws UsernameExistException {
if (EXIST_USERNAMES.contains(username)) {
throw new UsernameExistException ("用户名 [" + username + "] 已存在" );
}
if (password.length() < 6 ) {
throw new PasswordLengthException ("密码长度不能小于 6 位" );
}
EXIST_USERNAMES.add(username);
System.out.println("用户 [" + username + "] 注册成功!" );
}
public static void main (String[] args) {
try {
register("admin" , "123456" );
} catch (UsernameExistException e) {
System.out.println("注册失败:" + e.getMessage());
} catch (PasswordLengthException e) {
System.out.println("注册失败:" + e.getMessage());
}
try {
register("user2" , "123" );
} catch (UsernameExistException e) {
System.out.println("注册失败:" + e.getMessage());
} catch (PasswordLengthException e) {
System.out.println("注册失败:" + e.getMessage());
}
}
}
注册失败:用户名 [admin] 已存在 注册失败:密码长度不能小于 6 位
注意事项:自定义异常尽量继承 RuntimeException,避免强制调用者处理,减少代码冗余。
1.6 异常处理的最佳实践
1.6.1 核心原则
只捕获能处理的异常:对于无法处理的异常,应该向上抛出,让上层调用者处理。
捕获具体异常,而非笼统的 Exception:避免捕获所有异常,导致无法定位具体问题。
不要忽略异常:禁止空的 catch 块,至少要打印异常信息。
释放资源:使用 try-with-resources 或 finally 块确保资源被正确释放。
提供有意义的异常信息:异常信息要明确,便于开发者定位问题,比如'用户名 [admin] 已存在'。
1.6.2 常见误区 try {
} catch (NullPointerException e) {
}
try {
} catch (NullPointerException e) {
e.printStackTrace();
}
❌ 误区 2:捕获 Exception,处理所有异常
try {
} catch (Exception e) {
System.out.println("发生异常" );
}
try {
} catch (NullPointerException e) {
System.out.println("空指针异常" );
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组下标越界" );
}
public static int getLength (String str) {
try {
return str.length();
} catch (NullPointerException e) {
return 0 ;
}
}
public static int getLength (String str) {
if (str == null ) {
return 0 ;
}
return str.length();
}
1.7 实战案例:统一异常处理框架
1.7.1 需求分析 在实际项目中,需要一个统一的异常处理框架,将系统异常和业务异常进行统一封装,返回友好的错误信息。需求如下:
定义全局异常枚举,包含错误码和错误信息。
定义统一的返回结果类,封装接口返回数据。
定义全局异常处理器,捕获所有异常并统一处理。
1.7.2 代码实现
步骤 1:定义异常枚举
public enum ErrorCode {
SUCCESS(200 , "操作成功" ),
USERNAME_EXIST(1001 , "用户名已存在" ),
PASSWORD_LENGTH_ERROR(1002 , "密码长度不能小于 6 位" ),
SYSTEM_ERROR(500 , "系统内部异常" );
private final int code;
private final String message;
ErrorCode(int code, String message) {
this .code = code;
this .message = message;
}
public int getCode () {
return code;
}
public String getMessage () {
return message;
}
}
步骤 2:定义统一返回结果类
public class Result <T> {
private int code;
private String message;
private T data;
private Result () {}
public static <T> Result<T> success (T data) {
Result<T> result = new Result <>();
result.code = ErrorCode.SUCCESS.getCode();
result.message = ErrorCode.SUCCESS.getMessage();
result.data = data;
return result;
}
public static <T> Result<T> fail (ErrorCode errorCode) {
Result<T> result = new Result <>();
result.code = errorCode.getCode();
result.message = errorCode.getMessage();
return result;
}
public static <T> Result<T> fail (int code, String message) {
Result<T> result = new Result <>();
result.code = code;
result.message = message;
return result;
}
public int getCode () {
return code;
}
public void setCode (int code) {
this .code = code;
}
public String getMessage () {
return message;
}
public void setMessage (String message) {
this .message = message;
}
public T getData () {
return data;
}
public void setData (T data) {
this .data = data;
}
@Override
public String toString () {
return "Result{" + "code=" + code + ", message='" + message + "', data=" + data + '}' ;
}
}
步骤 3:定义全局业务异常
public class BusinessException extends RuntimeException {
private final int code;
public BusinessException (ErrorCode errorCode) {
super (errorCode.getMessage());
this .code = errorCode.getCode();
}
public BusinessException (int code, String message) {
super (message);
this .code = code;
}
public int getCode () {
return code;
}
}
步骤 4:定义全局异常处理器
public class GlobalExceptionHandler {
public static Result<?> handleBusinessException(BusinessException e) {
return Result.fail(e.getCode(), e.getMessage());
}
public static Result<?> handleSystemException(Exception e) {
e.printStackTrace();
return Result.fail(ErrorCode.SYSTEM_ERROR);
}
public static Result<?> handleException(Throwable e) {
if (e instanceof BusinessException) {
return handleBusinessException((BusinessException) e);
} else {
return handleSystemException((Exception) e);
}
}
}
步骤 5:测试统一异常处理框架 public class GlobalExceptionDemo {
public static void register (String username, String password) {
if ("admin" .equals(username)) {
throw new BusinessException (ErrorCode.USERNAME_EXIST);
}
if (password.length() < 6 ) {
throw new BusinessException (ErrorCode.PASSWORD_LENGTH_ERROR);
}
}
public static void main (String[] args) {
try {
register("admin" , "123456" );
} catch (Throwable e) {
Result<?> result = GlobalExceptionHandler.handleException(e);
System.out.println(result);
}
try {
register("user2" , "123" );
} catch (Throwable e) {
Result<?> result = GlobalExceptionHandler.handleException(e);
System.out.println(result);
}
try {
String str = null ;
System.out.println(str.length());
} catch (Throwable e) {
Result<?> result = GlobalExceptionHandler.handleException(e);
System.out.println(result);
}
}
}
Result{code=1001 , message='用户名已存在' , data =null } Result{code=1002 , message='密码长度不能小于 6 位' , data =null } java.lang.NullPointerException: Cannot invoke "String.length()" because "str" is null at GlobalExceptionDemo.main(GlobalExceptionDemo.java:28 ) Result{code=500 , message='系统内部异常' , data =null }
1.7.3 案例总结 这个统一异常处理框架是实际项目中的标准做法,它的优势在于:
统一了异常处理逻辑,减少重复代码。
区分了业务异常和系统异常,便于问题定位。
返回友好的错误信息,提升用户体验。
1.8 本章总结
异常分为 Error、Checked Exception 和 Unchecked Exception,其中 Error 无法处理,Unchecked Exception 建议通过代码规范避免。
异常处理的核心语法包括 try-catch-finally、throws、throw,try-with-resources 是推荐的资源释放方式。
自定义异常要贴合业务场景,优先继承 RuntimeException。
异常处理的最佳实践是:捕获具体异常、不忽略异常、释放资源、提供有意义的异常信息。
实际项目中要搭建统一异常处理框架,实现异常的统一封装和处理。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online