你是不是也遇到过这种情况写好的代码一运行就崩控制台满屏的红字报错却不知道到底哪里出了问题明明语法没错却总被各种NullPointerException、ArrayIndexOutOfBoundsException搞得头大别慌这篇文章就带你从 Java 异常的底层逻辑讲起手把手教你怎么优雅处理异常从基础语法到实战避坑一次性搞定异常处理再也不被 bug 追着跑。一、什么是异常Java 里的 “程序意外”异常就是程序运行过程中出现的非正常情况它会打断正常的指令流导致程序意外终止。比如你想访问数组第 10 个元素但数组只有 5 个元素或者你用一个null对象调用方法这些都是典型的异常场景。Java 里所有的异常类都继承自java.lang.Throwable它的体系结构可以分为三大类Error错误由 JVM 抛出的系统级错误比如OutOfMemoryError、StackOverflowError这类错误我们代码里基本处理不了只能通过优化代码来避免。Exception异常程序运行中可以被处理的异常又分为两种受检异常Checked Exception编译期就会被检查必须显式处理比如IOException、SQLException不处理的话代码直接编译不通过。非受检异常Unchecked Exception运行期才会出现编译期不强制处理比如NullPointerException、ArrayIndexOutOfBoundsException它们都继承自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异常的抛出与传递除了捕获异常我们还可以主动抛出异常让调用者来处理这就需要用到throws和throw关键字很多初学者容易把这两个搞混这里给你讲清楚区别。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; }坑点 3finally 块中修改返回值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(出错了); }❌ 危害如果代码同时抛出了NullPointerException和IOException你根本不知道到底是哪里出了问题。 ✅ 正确写法按从小到大的顺序捕获异常try { // 业务代码 } catch (NullPointerException e) { // 处理空指针 } catch (IOException e) { // 处理IO异常 } catch (Exception e) { // 兜底处理其他异常 }坑点 5频繁抛出异常❌ 危害异常对象的创建、栈信息的生成都有很大的性能开销在高并发场景下频繁抛出异常会严重影响系统性能。 ✅ 建议只在真正异常的场景下抛出异常正常业务逻辑用条件判断处理。六、异常处理的进阶try-with-resourcesJDK7前面我们用 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中使用比如FileInputStream、Connection等。七、总结异常处理的最佳实践优先用条件判断处理正常业务逻辑异常只处理非正常场景按从小到大的顺序捕获异常不要直接捕获 Exception异常捕获后一定要处理至少打印日志不要吞异常资源关闭优先使用 try-with-resources简化代码自定义异常贴合业务场景方便排查和统一处理finally 块只做资源回收不要写业务逻辑或 return 语句掌握了这些你就能写出优雅、健壮的异常处理代码再也不被 bug 追着跑啦。
一文吃透 Java 异常处理:从基础语法到实战避坑,再也不被 bug 追着跑
发布时间:2026/6/8 14:31:19
你是不是也遇到过这种情况写好的代码一运行就崩控制台满屏的红字报错却不知道到底哪里出了问题明明语法没错却总被各种NullPointerException、ArrayIndexOutOfBoundsException搞得头大别慌这篇文章就带你从 Java 异常的底层逻辑讲起手把手教你怎么优雅处理异常从基础语法到实战避坑一次性搞定异常处理再也不被 bug 追着跑。一、什么是异常Java 里的 “程序意外”异常就是程序运行过程中出现的非正常情况它会打断正常的指令流导致程序意外终止。比如你想访问数组第 10 个元素但数组只有 5 个元素或者你用一个null对象调用方法这些都是典型的异常场景。Java 里所有的异常类都继承自java.lang.Throwable它的体系结构可以分为三大类Error错误由 JVM 抛出的系统级错误比如OutOfMemoryError、StackOverflowError这类错误我们代码里基本处理不了只能通过优化代码来避免。Exception异常程序运行中可以被处理的异常又分为两种受检异常Checked Exception编译期就会被检查必须显式处理比如IOException、SQLException不处理的话代码直接编译不通过。非受检异常Unchecked Exception运行期才会出现编译期不强制处理比如NullPointerException、ArrayIndexOutOfBoundsException它们都继承自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异常的抛出与传递除了捕获异常我们还可以主动抛出异常让调用者来处理这就需要用到throws和throw关键字很多初学者容易把这两个搞混这里给你讲清楚区别。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; }坑点 3finally 块中修改返回值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(出错了); }❌ 危害如果代码同时抛出了NullPointerException和IOException你根本不知道到底是哪里出了问题。 ✅ 正确写法按从小到大的顺序捕获异常try { // 业务代码 } catch (NullPointerException e) { // 处理空指针 } catch (IOException e) { // 处理IO异常 } catch (Exception e) { // 兜底处理其他异常 }坑点 5频繁抛出异常❌ 危害异常对象的创建、栈信息的生成都有很大的性能开销在高并发场景下频繁抛出异常会严重影响系统性能。 ✅ 建议只在真正异常的场景下抛出异常正常业务逻辑用条件判断处理。六、异常处理的进阶try-with-resourcesJDK7前面我们用 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中使用比如FileInputStream、Connection等。七、总结异常处理的最佳实践优先用条件判断处理正常业务逻辑异常只处理非正常场景按从小到大的顺序捕获异常不要直接捕获 Exception异常捕获后一定要处理至少打印日志不要吞异常资源关闭优先使用 try-with-resources简化代码自定义异常贴合业务场景方便排查和统一处理finally 块只做资源回收不要写业务逻辑或 return 语句掌握了这些你就能写出优雅、健壮的异常处理代码再也不被 bug 追着跑啦。