从 TypeScript 到 Java(8):异常处理与错误机制 —— 让程序“有计划地失败”

系列导读:
在 TypeScript 中,我们几乎可以随意 throw 一个错误对象,也不一定非要 try...catch

但在 Java 中,异常是一等公民
它被严格分类、必须声明、还可能强制处理。

这套体系让 Java 程序更安全、更可预测,也更“唠叨”。

☕ 一句话理解

Java 的异常系统是一种“受控错误传播机制”:
程序在运行中如果遇到问题,不是立刻崩溃,而是通过 Exception 类体系 有序地传递、捕获与恢复。

⚙️ Java 异常的三层结构

Java 的异常都继承自顶层类 Throwable

Throwable
 ├── Error(严重错误,不建议捕获)
 └── Exception
      ├── Checked Exception(必须处理)
      └── RuntimeException(可选处理)

📘 简图:

Throwable
├── Exception
│   ├── IOException
│   ├── SQLException
│   ├── ClassNotFoundException
│   └── ...
│
└── RuntimeException
    ├── NullPointerException
    ├── IndexOutOfBoundsException
    ├── IllegalArgumentException
    └── ...

核心理念

  • Error:系统级崩溃(如内存溢出),不要处理。
  • Checked Exception:编译器强制处理(必须 try 或 throws)。
  • RuntimeException:程序逻辑错误,可选择性处理。

🧩 最基础的异常语法

try {
    int x = 10 / 0; // 抛出 ArithmeticException
} catch (ArithmeticException e) {
    System.out.println("不能除以0");
} finally {
    System.out.println("执行清理工作");
}

输出:

不能除以0
执行清理工作

关键点:

  • try:放可能出错的代码;
  • catch:捕获异常;
  • finally:无论是否出错都执行(常用于释放资源)。

🧠 Checked 与 Unchecked 异常区别

Checked Exception(受检异常)

必须显式处理,否则编译报错。

import java.io.FileReader;
import java.io.IOException;

public class Demo {
    public static void main(String[] args) throws IOException {
        FileReader reader = new FileReader("notfound.txt");
    }
}

编译器报错:

error: unreported exception IOException; must be caught or declared to be thrown

解决办法:

  • try...catch 捕获;
  • 或在方法签名中加 throws IOException
public static void main(String[] args) {
    try {
        FileReader reader = new FileReader("notfound.txt");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Unchecked Exception(非受检异常)

无需强制捕获。

int[] nums = {1, 2, 3};
System.out.println(nums[3]); // 抛出 IndexOutOfBoundsException

这类异常通常代表程序逻辑错误
不需要编译器干预,由开发者自行修复。

🔧 throws 与 throw 的区别

关键字 用途 位置
throw 实际抛出异常对象 方法体内
throws 声明该方法可能抛出的异常 方法签名

示例:

public static void risky() throws IOException {
    throw new IOException("文件读取失败");
}

调用时:

try {
    risky();
} catch (IOException e) {
    System.out.println(e.getMessage());
}

输出:

文件读取失败

🧱 自定义异常类

如果想创建自定义业务异常,可以继承 ExceptionRuntimeException

class UserNotFoundException extends Exception {
    public UserNotFoundException(String message) {
        super(message);
    }
}

使用:

public void findUser(String id) throws UserNotFoundException {
    if (id == null) {
        throw new UserNotFoundException("用户ID不能为空");
    }
}

💡 建议:

  • 继承 Exception → 强制调用者处理;
  • 继承 RuntimeException → 可选处理(常用于业务校验)。

📘 异常传播机制

当异常未被捕获时,会沿着调用栈向上传递

public static void main(String[] args) {
    methodA();
}

static void methodA() {
    methodB();
}

static void methodB() {
    int x = 1 / 0; // ArithmeticException 向上传递
}

最终由 JVM 捕获并打印堆栈信息。

输出示例:

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at Demo.methodB(Demo.java:10)
    at Demo.methodA(Demo.java:6)
    at Demo.main(Demo.java:2)

🔍 与 TypeScript 对比

功能 TypeScript Java
错误类型 任意对象 必须继承自 Throwable
异常声明 无需声明 Checked Exception 必须声明
捕获机制 try-catch-finally(可选) try-catch-finally(部分强制)
异常分类 三层结构:Error / Exception / RuntimeException
编译期检查

TypeScript 示例:

function readFile() {
  throw new Error("File not found");
}

try {
  readFile();
} catch (e) {
  console.error(e.message);
}

TypeScript 的错误完全是“运行时行为”;
Java 的异常同时是“语言语义 + 编译约束”。

🧩 最佳实践建议

1. 不滥用异常:

  • 异常不是控制流;
  • 不要用异常代替 if 判断。

2. 分层处理:

  • 底层抛出,顶层捕获;
  • 保留堆栈信息,方便排查。

3. 记录日志:

  • 使用 Logger 而不是 System.out.println
  • 结合 e.printStackTrace() 输出完整信息。

4. 自定义异常要语义清晰:

  • InvalidOrderExceptionUserNotFoundException

🧭 哲学层面理解

  • TypeScript 追求“开发便利”:错误出现时,更多依赖调试;
  • Java 追求“系统稳定”:编译期即要求程序员思考“错误可能发生的地方”;
  • Checked Exception 是一种“强制责任”的设计——提醒开发者对每一步承担后果。

☕ 这也正是企业级 Java 项目少见“莫名其妙崩溃”的原因。

🚀 实战练习

编写一个方法 readUserFile(path)

  1. 若文件不存在 → 抛出 FileNotFoundException
  2. 若内容为空 → 抛出自定义异常 EmptyFileException
  3. 主程序捕获两种异常并打印提示。

🔮 下一篇预告

第 9 篇:《泛型与集合框架:类型安全与灵活性的平衡》

在下一篇中,我们将讨论:

  • 为什么 Java 的泛型使用“类型擦除”实现,与 TypeScript 的泛型有何不同;
  • 通配符 ? extends 与 ? super 的使用场景与记忆口诀;
  • Java Collections Framework(List/Set/Map/Queue)的核心接口与常见实现,以及常见遍历和 Stream 用法。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐