【Java基础 | 10】异常处理入门:Throwable、try-catch-finally 与异常调用栈一次讲清

【Java基础】异常处理入门:Throwable、try-catch-finally 与异常调用栈一次讲清

    • 概念入口
    • 一、先建立异常处理的整体认知
      • [1.1 异常到底是什么](#1.1 异常到底是什么)
      • [1.2 为什么不能只靠 if-else 处理所有错误](#1.2 为什么不能只靠 if-else 处理所有错误)
    • [二、Throwable 体系:先分清谁是谁](#二、Throwable 体系:先分清谁是谁)
      • [2.1 Java 中能被抛出的对象都属于 Throwable](#2.1 Java 中能被抛出的对象都属于 Throwable)
      • [2.2 Error:通常不是业务代码该接住的东西](#2.2 Error:通常不是业务代码该接住的东西)
      • [2.3 Exception:程序有机会处理的异常](#2.3 Exception:程序有机会处理的异常)
    • 三、受检异常:编译器要求你给出处理方案
      • [3.1 什么是受检异常](#3.1 什么是受检异常)
      • [3.2 用文件读取理解受检异常](#3.2 用文件读取理解受检异常)
      • [3.3 受检异常适合表达什么](#3.3 受检异常适合表达什么)
    • 四、非受检异常:编译器不强制,但不代表可以忽略
      • [4.1 什么是非受检异常](#4.1 什么是非受检异常)
      • [4.2 非受检异常通常说明代码有问题](#4.2 非受检异常通常说明代码有问题)
      • [4.3 受检异常和非受检异常怎么区分](#4.3 受检异常和非受检异常怎么区分)
    • 五、try-catch:把可能出错的代码包起来
      • [5.1 基本语法](#5.1 基本语法)
      • [5.2 一个最小示例](#5.2 一个最小示例)
      • [5.3 catch 参数到底是什么](#5.3 catch 参数到底是什么)
    • [六、多个 catch:子类在前,父类在后](#六、多个 catch:子类在前,父类在后)
      • [6.1 为什么需要多个 catch](#6.1 为什么需要多个 catch)
      • [6.2 catch 的顺序为什么重要](#6.2 catch 的顺序为什么重要)
    • 七、finally:无论成功失败,都要收尾
      • [7.1 finally 的基本作用](#7.1 finally 的基本作用)
      • [7.2 finally 什么时候执行](#7.2 finally 什么时候执行)
      • [7.3 不建议在 finally 中 return](#7.3 不建议在 finally 中 return)
    • 八、异常传播:错误会沿着调用栈往上找人处理
      • [8.1 当前方法不处理,异常会继续往上抛](#8.1 当前方法不处理,异常会继续往上抛)
      • [8.2 用代码看异常传播](#8.2 用代码看异常传播)
      • [8.3 如果中间方法捕获了异常](#8.3 如果中间方法捕获了异常)
    • 九、怎么看异常栈信息
      • [9.1 异常栈至少看三件事](#9.1 异常栈至少看三件事)
      • [9.2 调用栈通常从"出错点"往"调用入口"排列](#9.2 调用栈通常从“出错点”往“调用入口”排列)
    • 十、常见运行时异常速查
      • [10.1 NullPointerException](#10.1 NullPointerException)
      • [10.2 ArrayIndexOutOfBoundsException](#10.2 ArrayIndexOutOfBoundsException)
      • [10.3 ArithmeticException](#10.3 ArithmeticException)
      • [10.4 ClassCastException](#10.4 ClassCastException)
      • [10.5 IllegalArgumentException](#10.5 IllegalArgumentException)
    • 十一、什么时候该捕获异常
      • [11.1 适合捕获的情况](#11.1 适合捕获的情况)
      • [11.2 不适合捕获的情况](#11.2 不适合捕获的情况)
    • 十二、本文先不展开哪些内容
    • 总结

🎬 博主名称: 超级苦力怕

🔥 个人专栏: 《Java 后端修炼手册》《Java 基础语言》

🚀 每一次思考都是突破的前奏,每一次复盘都是精进的开始!


文章元信息:

  • 适合读者: 已经理解方法调用、对象、继承和调用栈,准备学习异常处理机制的初学者
  • 前置知识: 建议先理解方法调用、对象创建、继承层次、栈帧和方法调用栈的基础概念

概念入口

写 Java 程序时,错误不可能完全避免:数组可能越界,参数可能不合法,文件可能不存在,网络可能断开,对象也可能是 null。如果所有错误都混在正常逻辑里处理,代码很快就会变得又乱又难维护。

异常处理要解决的核心问题,就是把"正常流程"和"出错后的处理流程"分开,并且允许错误沿着方法调用链向上传递。本文先整理异常处理的基础部分:Throwable 体系、ErrorException 的区别、受检异常与非受检异常、try-catch-finally 的执行规则,以及如何阅读异常调用栈。


一、先建立异常处理的整体认知

1.1 异常到底是什么

异常(Exception)用于表示程序运行过程中出现的非正常情况。

例如:

场景 典型异常
null 调用方法 NullPointerException
数组下标越界 ArrayIndexOutOfBoundsException
整数除以 0 ArithmeticException
类型强转失败 ClassCastException
文件不存在 FileNotFoundException
参数不合法 IllegalArgumentException

异常不是为了让程序"看起来不会错",而是为了让程序在出错时有一套清晰的处理机制。

它至少能解决三件事:

  • 把正常业务逻辑和错误处理逻辑分开。
  • 让错误可以沿着方法调用链向上传递。
  • 给调用者机会记录日志、提示用户、释放资源或尝试恢复。

1.2 为什么不能只靠 if-else 处理所有错误

当然,很多错误也可以用 if-else 提前判断。

例如:

java 复制代码
if (age < 0) {
    System.out.println("年龄不能为负数");
}

但有些错误不是简单判断就能完全处理的。

比如读取文件:

java 复制代码
FileInputStream in = new FileInputStream("a.txt");

文件是否存在、权限是否足够、磁盘是否异常,这些都可能受到外部环境影响。调用者不一定能在执行前全部判断清楚。

这里先不展开 IO 流本身,FileInputStream 只是用来说明"外部资源失败时很适合用异常表达"。文件读写、字节流、字符流和资源关闭,会放到后面的 IO 流章节系统讲。

这时异常机制就很有价值:它允许底层代码在失败时抛出异常,再由更合适的上层代码决定怎么处理。

💡 核心结论: if-else 更适合处理可预期的业务分支,异常更适合表达程序执行过程中出现的非正常失败。


二、Throwable 体系:先分清谁是谁

2.1 Java 中能被抛出的对象都属于 Throwable

Java 中所有可以被抛出的对象,顶层父类都是 Throwable

可以先记住这棵树:

text 复制代码
Throwable
├── Error
└── Exception
    ├── RuntimeException
    └── 其他受检异常

这几个概念的定位如下:

类型 作用
Throwable 所有可抛出对象的顶层父类
Error 严重错误,通常不由业务代码捕获处理
Exception 程序可能处理的异常
RuntimeException 运行时异常,通常表示代码逻辑错误或参数问题

初学阶段最容易混淆的是:ErrorException 都属于 Throwable,但它们的处理态度完全不同。

2.2 Error:通常不是业务代码该接住的东西

Error 通常表示 JVM 或系统层面的严重问题。

常见例子:

text 复制代码
OutOfMemoryError
StackOverflowError

比如上一篇讲内存时提到的:

  • 对象太多且无法回收,可能导致 OutOfMemoryError
  • 递归太深,调用栈被撑满,可能导致 StackOverflowError

这类问题一般不是靠业务代码 catch 一下就能解决的。

更正确的处理方向通常是:

  • 修复递归终止条件。
  • 优化对象创建和缓存管理。
  • 调整 JVM 参数。
  • 排查资源泄漏。
  • 改善系统容量和配置。

⚠️ 误区:所有 Throwable 都应该 catch

正确理解: Error 通常表示严重系统问题,业务代码一般不主动捕获。日常开发主要处理的是 Exception

2.3 Exception:程序有机会处理的异常

Exception 表示程序运行中可能被处理的异常。

它大致可以分成两类:

分类 是否受编译器强制处理 典型代表
受检异常 Checked Exception IOExceptionFileNotFoundExceptionSQLException
非受检异常 Unchecked Exception NullPointerExceptionIllegalArgumentExceptionArithmeticException

这两个概念非常重要,因为它们直接决定了代码写不写得过编译。


三、受检异常:编译器要求你给出处理方案

3.1 什么是受检异常

受检异常(Checked Exception)是编译器强制要求处理的异常。

如果一个方法可能抛出受检异常,你必须给出处理方案:

text 复制代码
要么 try-catch 捕获处理。
要么 throws 声明继续交给调用者。

本文重点先讲 try-catchthrows 会在下一篇"异常处理(下)"里继续展开。

3.2 用文件读取理解受检异常

例如读取文件:

java 复制代码
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class CheckedDemo {
    public static void main(String[] args) {
        try {
            FileInputStream in = new FileInputStream("a.txt");
        } catch (FileNotFoundException e) {
            System.out.println("文件不存在:" + e.getMessage());
        }
    }
}

FileInputStream 打开文件时,文件可能不存在。这不是简单的代码写错,而是外部环境导致的失败。

所以编译器会要求你处理 FileNotFoundException

如果不处理,代码会直接编译失败。

再次强调一下:这个例子只服务于"受检异常"这个知识点。至于 FileInputStream 如何读取文件、什么时候关闭、字节流和字符流有什么区别,会放到后面的 IO 流(上)里展开。

3.3 受检异常适合表达什么

受检异常更适合表达那些"调用方可能有恢复策略"的问题。

比如:

场景 为什么适合受检异常
文件不存在 可以提示用户重新选择文件
网络连接失败 可以重试或提示稍后再试
数据库访问失败 可以回滚事务、记录日志、返回错误结果
外部资源不可用 调用方可能切换备用方案

受检异常的重点不是"错误更严重",而是:调用方应该被提醒,这件事可能失败,并且你需要决定怎么处理。


四、非受检异常:编译器不强制,但不代表可以忽略

4.1 什么是非受检异常

非受检异常(Unchecked Exception)通常指 RuntimeException 及其子类。

常见例子:

异常 常见原因
NullPointerException null 调用方法或访问字段
ArrayIndexOutOfBoundsException 数组下标越界
ArithmeticException 整数除以 0
ClassCastException 类型强转失败
IllegalArgumentException 方法参数不合法

这类异常编译器不会强制你捕获。

例如:

java 复制代码
public class RuntimeDemo {
    public static void main(String[] args) {
        int x = 10 / 0;
        System.out.println(x);
    }
}

这段代码可以通过编译,但运行时会抛出:

text 复制代码
ArithmeticException

4.2 非受检异常通常说明代码有问题

运行时异常经常意味着:

  • 参数没有校验。
  • 对象状态不符合预期。
  • 数组下标计算错误。
  • 类型判断不充分。
  • 调用顺序不合理。

例如:

java 复制代码
public static int getLength(String text) {
    return text.length();
}

如果传入 null

java 复制代码
getLength(null);

就会出现 NullPointerException

这时更好的写法通常不是在内部随手 catch,而是先明确方法的参数约束:

java 复制代码
public static int getLength(String text) {
    if (text == null) {
        throw new IllegalArgumentException("text 不能为 null");
    }
    return text.length();
}

这里的 throw 会在下一篇详细讲。本文先理解它的意图:让错误在更明确的位置暴露出来,而不是等到更深处变成难排查的空指针。

4.3 受检异常和非受检异常怎么区分

可以用这张表建立直觉:

对比项 受检异常 非受检异常
编译器是否强制处理 强制 不强制
常见父类 Exception 但不属于 RuntimeException RuntimeException 及其子类
常见原因 外部环境、资源访问、调用方可恢复的问题 代码逻辑错误、参数错误、状态错误
处理方式 捕获处理或继续声明抛出 通常优先修复代码、校验参数
示例 IOExceptionSQLException NullPointerExceptionIllegalArgumentException

💡 核心结论: 受检异常强调"调用方必须面对这个失败",非受检异常更多是在提醒"代码逻辑或输入状态可能有问题"。


五、try-catch:把可能出错的代码包起来

5.1 基本语法

try-catch 是最基础的异常处理结构。

java 复制代码
try {
    // 可能出错的代码
} catch (ExceptionType e) {
    // 异常处理逻辑
}

执行逻辑可以这样理解:

步骤 发生什么
进入 try 执行可能出错的代码
出现异常 JVM 创建异常对象
查找 catch 看有没有类型匹配的处理块
匹配成功 执行对应 catch 中的代码
匹配失败 异常继续向调用者传播

5.2 一个最小示例

java 复制代码
public class TryCatchDemo {
    public static void main(String[] args) {
        try {
            int x = 10 / 0;
            System.out.println(x);
        } catch (ArithmeticException e) {
            System.out.println("除数不能为 0");
        }
    }
}

运行结果:

text 复制代码
除数不能为 0

这里要注意:try 中一旦出现异常,异常发生位置后面的代码不会继续执行,而是直接跳到匹配的 catch

例如:

java 复制代码
try {
    System.out.println("A");
    int x = 10 / 0;
    System.out.println("B");
} catch (ArithmeticException e) {
    System.out.println("C");
}

输出是:

text 复制代码
A
C

B 不会输出,因为异常已经打断了 try 中后续代码。

5.3 catch 参数到底是什么

看这一句:

java 复制代码
catch (ArithmeticException e)

它的含义是:如果 try 中抛出的异常对象是 ArithmeticException 类型,或者是它的子类,就进入这个 catch

e 是异常对象的引用,可以用它拿到异常信息:

java 复制代码
try {
    int x = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println(e.getClass().getName());
    System.out.println(e.getMessage());
}

输出类似:

text 复制代码
java.lang.ArithmeticException
/ by zero

调试时,不要只看"程序报错了",要先看异常类型,再看异常消息。


六、多个 catch:子类在前,父类在后

6.1 为什么需要多个 catch

同一段代码可能出现不同类型的异常。

为了不提前展开 IO 流,这里用字符串转数字来说明多个 catch 的写法。

可以写多个 catch

java 复制代码
try {
    int number = Integer.parseInt("abc");
    System.out.println(number);
} catch (NumberFormatException e) {
    System.out.println("字符串不能转换成数字");
} catch (IllegalArgumentException e) {
    System.out.println("参数不合法");
}

6.2 catch 的顺序为什么重要

多个 catch 的顺序必须遵守一个原则:

text 复制代码
子类异常写前面,父类异常写后面。

因为异常匹配是从上往下进行的。

例如:

java 复制代码
try {
    int number = Integer.parseInt("abc");
} catch (IllegalArgumentException e) {
    System.out.println("参数不合法");
} catch (NumberFormatException e) {
    System.out.println("字符串不能转换成数字");
}

这段代码会编译错误。

原因是 NumberFormatExceptionIllegalArgumentException 的子类。如果父类 IllegalArgumentException 已经写在前面,后面的 NumberFormatException 永远没有机会执行。

正确写法是:

java 复制代码
try {
    int number = Integer.parseInt("abc");
} catch (NumberFormatException e) {
    System.out.println("字符串不能转换成数字");
} catch (IllegalArgumentException e) {
    System.out.println("参数不合法");
}

⚠️ 误区:catch 顺序随便写

正确理解: 多个 catch 要从具体到宽泛。子类在前,父类在后。


七、finally:无论成功失败,都要收尾

7.1 finally 的基本作用

finally 通常用于放置必须执行的收尾代码。

基本结构:

java 复制代码
try {
    // 使用资源
} catch (Exception e) {
    // 处理异常
} finally {
    // 释放资源、关闭连接、清理状态
}

常见场景包括:

  • 关闭文件流。
  • 关闭网络连接。
  • 释放锁或其他资源。
  • 清理临时状态。

7.2 finally 什么时候执行

通常情况下,无论 try 中是否出现异常,finally 都会执行。

例如:

java 复制代码
public class FinallyDemo {
    public static void main(String[] args) {
        try {
            System.out.println("try");
            int x = 10 / 0;
        } catch (ArithmeticException e) {
            System.out.println("catch");
        } finally {
            System.out.println("finally");
        }
    }
}

输出:

text 复制代码
try
catch
finally

如果没有异常:

java 复制代码
try {
    System.out.println("try");
} finally {
    System.out.println("finally");
}

输出:

text 复制代码
try
finally

所以可以先记住一句话:

text 复制代码
finally 更像是"无论前面成功还是失败,都尽量做一次收尾"。

这里说"通常"和"尽量",是因为极端情况也可能让 finally 来不及执行,例如 JVM 直接退出、进程被强杀、机器断电等。入门阶段重点掌握正常控制流下的规则即可。

7.3 不建议在 finally 中 return

finally 里可以写很多代码,但不建议写 return

看一个危险示例:

java 复制代码
public static int test() {
    try {
        return 1;
    } finally {
        return 2;
    }
}

这个方法最终返回的是:

text 复制代码
2

finally 中的 return 会覆盖 try 中的返回结果。

更糟糕的是,如果 try 中本来抛出了异常,finally 中的 return 还可能把异常吞掉,让问题变得非常难排查。

⚠️ 误区:finally 中 return 只是普通返回

正确理解: finally 中的 return 可能覆盖正常返回值或异常信息,实际开发中应尽量避免。


八、异常传播:错误会沿着调用栈往上找人处理

8.1 当前方法不处理,异常会继续往上抛

如果当前方法没有处理异常,异常会沿着调用链继续向上传播。

假设有这样一条调用链:

text 复制代码
main -> methodA -> methodB -> methodC

如果 methodC 抛出异常,JVM 会按下面的顺序查找处理者:

步骤 查找位置
1 methodC 自己有没有匹配的 catch
2 没有就回到 methodB
3 methodB 没有就回到 methodA
4 methodA 没有就回到 main
5 如果一直没人处理,程序终止并打印异常栈信息

这就是为什么异常和上一篇讲的"方法调用栈"关系非常密切。

异常不是凭空跳走的,它是沿着方法调用路径一级一级往回找处理者。

8.2 用代码看异常传播

java 复制代码
public class ExceptionFlowDemo {
    public static void main(String[] args) {
        methodA();
    }

    public static void methodA() {
        methodB();
    }

    public static void methodB() {
        methodC();
    }

    public static void methodC() {
        int x = 10 / 0;
    }
}

如果没有任何地方捕获异常,运行后会看到类似信息:

text 复制代码
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at ExceptionFlowDemo.methodC(ExceptionFlowDemo.java:16)
    at ExceptionFlowDemo.methodB(ExceptionFlowDemo.java:12)
    at ExceptionFlowDemo.methodA(ExceptionFlowDemo.java:8)
    at ExceptionFlowDemo.main(ExceptionFlowDemo.java:4)

这段输出就是异常栈信息。

它告诉你:

  • 异常类型是 ArithmeticException
  • 异常消息是 / by zero
  • 最直接出错位置在 methodC
  • 调用路径是 main -> methodA -> methodB -> methodC

8.3 如果中间方法捕获了异常

如果 methodA 负责捕获:

java 复制代码
public static void main(String[] args) {
    methodA();
}

public static void methodA() {
    try {
        methodB();
    } catch (ArithmeticException e) {
        System.out.println("methodA 处理了异常");
    }
}

public static void methodB() {
    methodC();
}

public static void methodC() {
    int x = 10 / 0;
}

那么异常会从 methodC 传播到 methodB,再传播到 methodA,最终被 methodAcatch 捕获。

程序输出:

text 复制代码
methodA 处理了异常

💡 核心结论: 异常传播不是乱跳,而是沿着方法调用栈向上查找匹配的处理逻辑。


九、怎么看异常栈信息

9.1 异常栈至少看三件事

很多初学者看到一大段红色报错,会下意识只看最后一行。

但异常栈真正要看的是三件事:

看什么 作用
异常类型 判断是哪类错误
异常消息 获取具体失败原因
第一处自己代码的行号 找到最值得排查的位置

例如:

text 复制代码
java.lang.NullPointerException: Cannot invoke "String.length()" because "text" is null
    at Demo.getLength(Demo.java:8)
    at Demo.main(Demo.java:4)

可以这样读:

  • 异常类型:NullPointerException
  • 异常消息:textnull,却调用了 length()
  • 第一处自己代码:Demo.java:8

所以排查时应该先去看 Demo.java 第 8 行,而不是被整段栈信息吓住。

9.2 调用栈通常从"出错点"往"调用入口"排列

异常栈中越靠上的位置,通常越接近真正出错点。

例如:

text 复制代码
at Demo.methodC(Demo.java:16)
at Demo.methodB(Demo.java:12)
at Demo.methodA(Demo.java:8)
at Demo.main(Demo.java:4)

这说明:

text 复制代码
main 调 methodA
methodA 调 methodB
methodB 调 methodC
methodC 出错

阅读时可以从上往下找出错点,也可以从下往上还原调用链。

⚠️ 误区:只看异常栈最后一行

正确理解: 最后一行往往只是入口方法,真正出错点通常在异常栈上方第一处自己的业务代码。


十、常见运行时异常速查

10.1 NullPointerException

NullPointerException 是最常见的运行时异常之一。

典型原因:

java 复制代码
String text = null;
System.out.println(text.length());

问题是:text 没有指向任何字符串对象,却调用了 length()

常见处理思路:

  • 明确哪些参数允许为 null,哪些不允许。
  • 在方法入口处做参数校验。
  • 不要让对象处于半初始化状态。
  • 读异常消息和出错行号,不要盲猜。

10.2 ArrayIndexOutOfBoundsException

数组下标越界:

java 复制代码
int[] nums = {1, 2, 3};
System.out.println(nums[3]);

数组长度是 3,合法下标是:

text 复制代码
0, 1, 2

访问 nums[3] 就会越界。

10.3 ArithmeticException

整数除以 0:

java 复制代码
int x = 10 / 0;

会抛出:

text 复制代码
ArithmeticException

注意,整数除以 0 会报错;浮点数除以 0 的表现和整数不同,这属于数值运算规则,可以在数据类型与运算符中单独理解。

10.4 ClassCastException

类型强转失败:

java 复制代码
Object obj = "hello";
Integer num = (Integer) obj;

obj 实际指向的是 String 对象,却被强转成 Integer,运行时会抛出 ClassCastException

在继承和多态场景中,强转前可以使用 instanceof 判断。

10.5 IllegalArgumentException

IllegalArgumentException 通常用于表示方法参数不合法。

例如:

java 复制代码
public static void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("age 不能为负数");
    }
}

它比空指针或数组越界更主动,因为它把错误拦在了方法入口。


十一、什么时候该捕获异常

11.1 适合捕获的情况

异常不是看到就一定要 catch

适合捕获的情况通常有:

情况 示例
能恢复 文件不存在时让用户重新选择
能给用户友好提示 输入格式错误时提示重新输入
能补充日志后继续流程 某个非核心任务失败,但主流程可以继续
能转换成更合适的错误结果 后端接口返回统一错误响应
能释放资源 捕获或最终收尾时关闭文件、连接

捕获异常的前提是:你知道接下来要做什么。

11.2 不适合捕获的情况

不适合捕获的情况也很常见:

错误写法 问题
catch (Exception e) {} 什么都不做,问题被隐藏
捕获后继续使用错误状态 程序可能进入更混乱的状态
到处捕获宽泛的 Exception 真正错误类型被掩盖
用异常控制正常流程 性能和可读性都不好
只打印一句"出错了" 排查时没有足够信息

例如:

java 复制代码
try {
    int x = 10 / 0;
} catch (Exception e) {
}

这段代码最危险的地方不是"捕获了异常",而是捕获后什么也不做。

程序表面上继续运行,但真正的问题已经被藏起来了。

⚠️ 误区:catch 越多越安全

正确理解: 捕获异常不是为了把错误压下去,而是为了在合适的位置处理、记录或继续交给上层。


十二、本文先不展开哪些内容

为了让这一篇聚焦异常处理基础,下面这些内容先不深入展开:

内容 为什么不在本文展开
throw 关键字 下一篇会结合主动抛异常系统讲
throws 声明 本文只提基本概念,下一篇讲方法签名和调用方责任
自定义异常类 需要先理解异常体系和主动抛出
异常链与异常包装 适合放在进阶部分讲
try-with-resources 与资源关闭关系密切,后续可结合 I/O 展开
日志框架中的异常记录 属于工程实践内容,后续结合后端项目更合适

这一篇的目标,是先让你知道 Java 异常怎么分类、怎么捕获、怎么传播,以及怎么读懂异常栈。


总结

误区速查表

常见误区 更准确的理解
所有 Throwable 都应该捕获 日常业务主要处理 Exception,通常不主动捕获 Error
受检异常更严重 受检异常只是编译器强制处理,不等于一定更严重
运行时异常不用管 编译器不强制捕获,但通常意味着代码逻辑或参数状态有问题
catch (Exception e) {} 很安全 catch 会隐藏问题,是非常危险的写法
多个 catch 顺序随便写 子类异常要写前面,父类异常写后面
finallyreturn 没问题 可能覆盖返回值或吞掉异常,不建议这样写
异常栈只看最后一行 应先看异常类型、异常消息、第一处自己代码的行号

知识点总表

这一篇主要讲清楚了 Java 异常处理的基础知识:

模块 需要掌握的核心点
异常的作用 表示程序执行中的非正常情况,把正常逻辑和错误处理分开
Throwable 所有可抛出对象的顶层父类
Error 严重错误,通常不由业务代码主动捕获
Exception 程序可能处理的异常
受检异常 编译器强制处理,要么捕获,要么继续声明抛出
非受检异常 编译器不强制捕获,通常表示逻辑错误、参数错误或状态错误
try-catch 捕获并处理匹配类型的异常
多个 catch 子类异常在前,父类异常在后
finally 通常用于释放资源或执行收尾逻辑
异常传播 当前方法不处理时,异常沿调用栈向上传播
异常栈 能看到异常类型、消息、出错行号和调用路径
捕获原则 能处理再捕获,不能处理就不要把问题悄悄吞掉

最后记住四句话:

  • 异常不是为了掩盖错误,而是为了让错误有清晰的传播和处理机制。
  • Error 通常不由业务代码处理,日常重点是 Exception
  • 受检异常编译器强制处理,运行时异常更常提示代码逻辑问题。
  • 读异常栈时,先看异常类型,再看第一处自己的代码行号。

相关推荐
莫逸风1 小时前
【AgentScope】3. 工作空间(Workspace)详解
java·ai·agent·springai·agentscope
Devin~Y1 小时前
从Spring Boot到AI Agent:大厂Java微服务面试三轮实战问答解析
java·spring boot·redis·spring cloud·微服务·ai·kafka
brave_zhao1 小时前
http 403 HTTP 403(Forbidden)表示服务器理解请求,但拒绝授权访问
java
爱吃羊的老虎1 小时前
【JAVA】python转java:Spring Boot 如何处理 Web 请求
java·前端·spring boot·http
装不满的克莱因瓶1 小时前
DDD 设计与 Maven 多模块拆分:从单体项目到领域驱动架构实践
java·架构·maven·ddd
码不停蹄的玄黓1 小时前
SpringBoot 循环依赖解决方案
java·spring boot·后端
装不满的克莱因瓶1 小时前
Spring 全家桶与 Spring 6 新特性详解:从 IoC 到云原生时代
java·spring·云原生·jdk·新特性·spring6
ch.ju1 小时前
Java程序设计(第3版)第四章——私有属性
java·开发语言
装不满的克莱因瓶1 小时前
JSON 处理与内嵌 Tomcat 部署:Spring Boot 如何实现前后端数据交互与一键启动?
java·spring boot·spring·架构·tomcat·json