news 2026/6/25 14:09:26

一文吃透 Java 异常处理:从基础语法到实战避坑,再也不被 bug 追着跑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一文吃透 Java 异常处理:从基础语法到实战避坑,再也不被 bug 追着跑

你是不是也遇到过这种情况?写好的代码一运行就崩,控制台满屏的红字报错,却不知道到底哪里出了问题?明明语法没错,却总被各种NullPointerExceptionArrayIndexOutOfBoundsException搞得头大?

别慌,这篇文章就带你从 Java 异常的底层逻辑讲起,手把手教你怎么优雅处理异常,从基础语法到实战避坑,一次性搞定异常处理,再也不被 bug 追着跑。


一、什么是异常?Java 里的 “程序意外”

异常,就是程序运行过程中出现的非正常情况,它会打断正常的指令流,导致程序意外终止。比如你想访问数组第 10 个元素,但数组只有 5 个元素;或者你用一个null对象调用方法,这些都是典型的异常场景。

Java 里所有的异常类,都继承自java.lang.Throwable,它的体系结构可以分为三大类:

  • Error(错误):由 JVM 抛出的系统级错误,比如OutOfMemoryErrorStackOverflowError,这类错误我们代码里基本处理不了,只能通过优化代码来避免。
  • Exception(异常):程序运行中可以被处理的异常,又分为两种:
    1. 受检异常(Checked Exception):编译期就会被检查,必须显式处理,比如IOExceptionSQLException,不处理的话代码直接编译不通过。
    2. 非受检异常(Unchecked Exception):运行期才会出现,编译期不强制处理,比如NullPointerExceptionArrayIndexOutOfBoundsException,它们都继承自RuntimeException

这里给你整理了一张常见异常对照表,帮你快速分清它们:

异常类型常见代表触发场景
非受检异常NullPointerException调用了 null 对象的方法 / 属性
非受检异常ArrayIndexOutOfBoundsException访问了数组不存在的下标
非受检异常ArithmeticException整数除以 0
非受检异常ClassCastException类型转换错误(比如把 String 强转成 Integer)
受检异常IOException文件读写、网络请求失败
受检异常SQLException数据库操作出错

二、异常处理核心语法:try-catch-finally 全解析

Java 里处理异常最基础的语法就是try-catch-finally,它的核心逻辑就是:尝试执行可能出错的代码,出错了就捕获处理,最后无论如何都要执行收尾操作

1. 基础语法结构

public class ExceptionDemo { public static void main(String[] args) { int[] arr = {1, 2, 3}; try { // 可能会出现异常的代码 System.out.println("尝试访问数组第5个元素:" + arr[5]); System.out.println("这段代码在异常发生后不会执行"); } catch (ArrayIndexOutOfBoundsException e) { // 捕获并处理异常 System.out.println("捕获到数组下标越界异常:" + e.getMessage()); } finally { // 无论是否发生异常,这段代码一定会执行 System.out.println("finally块执行:关闭资源/收尾操作"); } System.out.println("程序继续运行,没有意外终止"); } }

运行结果

plaintext

捕获到数组下标越界异常:Index 5 out of bounds for length 3 finally块执行:关闭资源/收尾操作 程序继续运行,没有意外终止

2. 多异常捕获的 3 种写法

实际开发中,一段代码可能会抛出多种异常,这里给你整理了 3 种常用的捕获方式:

方式 1:多个 catch 块(推荐,不同异常不同处理)
try { int a = 1 / 0; int[] arr = {1,2,3}; System.out.println(arr[5]); } catch (ArithmeticException e) { System.out.println("算术异常:除数不能为0"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("数组下标越界:下标超出数组长度"); }
方式 2:合并捕获(JDK7 + 支持,异常处理逻辑相同时用)
try { int a = 1 / 0; int[] arr = {1,2,3}; System.out.println(arr[5]); } catch (ArithmeticException | ArrayIndexOutOfBoundsException e) { System.out.println("捕获到运行异常:" + e.getMessage()); }
方式 3:捕获父类 Exception(不推荐,会吞掉所有异常)
try { // 可能抛出多种异常的代码 } catch (Exception e) { System.out.println("捕获到异常:" + e.getMessage()); }

⚠️ 注意:直接捕获Exception会让你分不清具体是什么异常,不利于排查问题,只有在特殊场景下才建议使用。

3. finally 块的特殊场景

finally 块的核心作用是资源回收,比如关闭文件流、数据库连接、网络连接等,这些资源无论程序是否出错,都必须被关闭,否则会造成资源泄漏。

import java.io.FileWriter; import java.io.IOException; public class FinallyDemo { public static void main(String[] args) { FileWriter writer = null; try { writer = new FileWriter("test.txt"); writer.write("测试写入内容"); System.out.println("写入完成"); } catch (IOException e) { System.out.println("写入失败:" + e.getMessage()); } finally { try { if (writer != null) { writer.close(); // 无论是否写入成功,都要关闭流 System.out.println("文件流已关闭"); } } catch (IOException e) { e.printStackTrace(); } } } }

⚠️ 一个特殊情况:当在 try/catch 块中执行System.exit(0);时,finally 块不会执行,因为这会直接终止 JVM 进程。


三、throws 和 throw:异常的抛出与传递

除了捕获异常,我们还可以主动抛出异常,让调用者来处理,这就需要用到throwsthrow关键字,很多初学者容易把这两个搞混,这里给你讲清楚区别。

1. throws 关键字:声明异常,抛给调用者处理

throws用在方法声明上,表示这个方法可能会抛出某些异常,不处理,交给调用者来处理。

import java.io.FileInputStream; import java.io.IOException; public class ThrowsDemo { // 声明方法可能抛出IOException,交给调用者处理 public static void readFile(String path) throws IOException { FileInputStream fis = new FileInputStream(path); 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 (IOException e) { System.out.println("文件读取失败:" + e.getMessage()); } } }

2. throw 关键字:手动抛出异常

throw用在方法体内,手动抛出一个具体的异常对象,通常用于参数校验等场景。

public class ThrowDemo { // 定义一个方法,计算两个数的商 public static int divide(int a, int b) { if (b == 0) { // 手动抛出算术异常 throw new ArithmeticException("除数不能为0!"); } return a / b; } public static void main(String[] args) { try { int result = divide(10, 0); System.out.println("结果:" + result); } catch (ArithmeticException e) { System.out.println("捕获到异常:" + e.getMessage()); } } }

3. throws 和 throw 的核心区别

关键字使用位置作用
throws方法声明后声明方法可能抛出的异常,交给调用者处理
throw方法体内手动抛出一个具体的异常对象

四、自定义异常:打造属于自己的业务异常

实际开发中,JDK 自带的异常可能无法满足业务场景,比如用户余额不足、订单状态异常等,这时候我们可以自定义异常,让错误信息更清晰,也方便统一处理。

1. 自定义受检异常(继承 Exception)

// 自定义余额不足异常 public class BalanceNotEnoughException extends Exception { // 无参构造 public BalanceNotEnoughException() { super(); } // 带错误信息的构造 public BalanceNotEnoughException(String message) { super(message); } }

2. 自定义非受检异常(继承 RuntimeException)

// 自定义订单状态异常 public class OrderStatusException extends RuntimeException { public OrderStatusException() { super(); } public OrderStatusException(String message) { super(message); } }

3. 自定义异常的使用场景

public class CustomExceptionDemo { // 模拟支付方法 public static void pay(double balance, double amount) throws BalanceNotEnoughException { if (balance < amount) { throw new BalanceNotEnoughException("余额不足,当前余额:" + balance + ",支付金额:" + amount); } System.out.println("支付成功,剩余余额:" + (balance - amount)); } public static void main(String[] args) { try { pay(100, 200); } catch (BalanceNotEnoughException e) { System.out.println("支付失败:" + e.getMessage()); } } }

自定义异常的好处:

  • 错误信息更贴合业务,排查问题更高效
  • 可以和全局异常处理器配合,实现统一的错误响应

五、异常处理的实战避坑指南

很多新手在写异常处理时,会犯一些低级错误,导致代码不仅没解决问题,还埋下了隐患,这里给你整理了 5 个常见坑点,帮你避开雷区。

坑点 1:捕获异常后什么都不做(吞异常)

// 错误写法 try { // 业务代码 } catch (Exception e) { // 什么都不写,异常被吞掉了 }

❌ 危害:异常发生了却没有任何日志,排查问题时根本不知道哪里出了错。 ✅ 正确写法:至少打印异常信息或日志

try { // 业务代码 } catch (Exception e) { e.printStackTrace(); // 打印异常栈信息 // 或者用日志框架记录:logger.error("业务异常", e); }

坑点 2:用 catch 块代替 if 判断

// 错误写法:用异常来处理正常业务逻辑 try { int a = 10 / 0; } catch (ArithmeticException e) { System.out.println("除数不能为0"); }

❌ 危害:异常处理的性能开销远大于 if 判断,用异常处理正常业务逻辑会严重影响性能。 ✅ 正确写法:先做参数校验

int b = 0; if (b == 0) { System.out.println("除数不能为0"); } else { int a = 10 / b; }

坑点 3:finally 块中修改返回值

public class FinallyReturnDemo { public static int test() { int a = 10; try { return a; } finally { a = 20; } } public static void main(String[] args) { System.out.println(test()); // 输出10,不是20! } }

⚠️ 原因:try 块中的 return 会先把返回值保存起来,finally 块中修改变量不会影响返回结果,不建议在 finally 中写 return 或修改返回值。

坑点 4:捕获范围过大的异常

// 错误写法:直接捕获Exception,分不清具体异常 try { // 业务代码 } catch (Exception e) { System.out.println("出错了"); }

❌ 危害:如果代码同时抛出了NullPointerExceptionIOException,你根本不知道到底是哪里出了问题。 ✅ 正确写法:按从小到大的顺序捕获异常

try { // 业务代码 } catch (NullPointerException e) { // 处理空指针 } catch (IOException e) { // 处理IO异常 } catch (Exception e) { // 兜底处理其他异常 }

坑点 5:频繁抛出异常

❌ 危害:异常对象的创建、栈信息的生成都有很大的性能开销,在高并发场景下频繁抛出异常,会严重影响系统性能。 ✅ 建议:只在真正异常的场景下抛出异常,正常业务逻辑用条件判断处理。


六、异常处理的进阶:try-with-resources(JDK7+)

前面我们用 finally 块关闭资源,代码非常繁琐,JDK7 推出了try-with-resources语法,自动实现资源关闭,不用再手动写 finally 块了。

1. 传统写法(finally 关闭资源)

import java.io.FileWriter; import java.io.IOException; public class FinallyOldDemo { public static void main(String[] args) { FileWriter writer = null; try { writer = new FileWriter("test.txt"); writer.write("测试内容"); } catch (IOException e) { e.printStackTrace(); } finally { try { if (writer != null) { writer.close(); } } catch (IOException e) { e.printStackTrace(); } } } }

2. try-with-resources 写法(推荐)

import java.io.FileWriter; import java.io.IOException; public class TryWithResourcesDemo { public static void main(String[] args) { // 在try()中声明资源,程序结束后自动关闭 try (FileWriter writer = new FileWriter("test.txt")) { writer.write("测试内容"); System.out.println("写入完成"); } catch (IOException e) { e.printStackTrace(); } } }

⚠️ 注意:只有实现了AutoCloseable接口的类,才能在try-with-resources中使用,比如FileInputStreamConnection等。


七、总结:异常处理的最佳实践

  1. 优先用条件判断处理正常业务逻辑,异常只处理非正常场景
  2. 按从小到大的顺序捕获异常,不要直接捕获 Exception
  3. 异常捕获后一定要处理,至少打印日志,不要吞异常
  4. 资源关闭优先使用 try-with-resources,简化代码
  5. 自定义异常贴合业务场景,方便排查和统一处理
  6. finally 块只做资源回收,不要写业务逻辑或 return 语句

掌握了这些,你就能写出优雅、健壮的异常处理代码,再也不被 bug 追着跑啦。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/8 14:29:28

爷青回!中国足球小将用 8 年时间再次证明自己

2026 年 6 月 2 日&#xff0c;意大利 SIGISMONDI 杯赛场&#xff0c;中国足球小将 2014 队点球 5-4 击败英超埃弗顿 U12 梯队&#xff0c;以七战全胜、进 21 球仅失 2 球的战绩夺冠。这是该赛事创办 37 年来&#xff0c;首次有非欧洲球队登顶。八年前的 2018 年&#xff0c;足…

作者头像 李华
网站建设 2026/6/8 14:28:20

怎样3步掌握《缺氧》存档修改:Duplicity网页编辑器完整指南

怎样3步掌握《缺氧》存档修改&#xff1a;Duplicity网页编辑器完整指南 【免费下载链接】oni-duplicity A web-hosted, locally-running save editor for Oxygen Not Included. 项目地址: https://gitcode.com/gh_mirrors/on/oni-duplicity 还在为《缺氧》游戏中的资源短…

作者头像 李华
网站建设 2026/6/8 14:28:05

7-Zip-zstd终极指南:解锁现代压缩算法的完整解决方案

7-Zip-zstd终极指南&#xff1a;解锁现代压缩算法的完整解决方案 【免费下载链接】7-Zip-zstd 7-Zip with support for Brotli, Fast-LZMA2, Lizard, LZ4, LZ5 and Zstandard 项目地址: https://gitcode.com/gh_mirrors/7z/7-Zip-zstd 还在为传统压缩工具的速度和压缩比…

作者头像 李华
网站建设 2026/6/8 14:25:55

南宁及周边地区车棚价格与款式分析

在南宁及周边地区&#xff0c;车棚的市场正朝着多样化和专业化发展。我们看到&#xff0c;南宁市的车棚销售情况稳中有升&#xff0c;种类繁多&#xff0c;以满足不同消费者的需求。从价格来看&#xff0c;南宁车棚价格范围大致在3000元至9000元不等&#xff0c;涵盖了各式各样…

作者头像 李华