1. 类 Throwable:所有异常的根
现在我们来详细说明 Java 的异常系统是如何构成的:什么是 Throwable,Exception 和 Error 有何不同,以及“受检”与“非受检”异常意味着什么。这是你在程序中正确处理错误的基础。
在 Java 中,所有 异常与错误都是从 java.lang.Throwable 类继承而来的对象。
Throwable 是 Java 中整个问题处理层次结构的“祖先”。
示意:
Throwable
├── Exception
└── Error
Throwable 是一切可以在 Java 中被“抛出”(throw)与“捕获”(catch)的基类。它不能被直接使用——而是作为更具体错误类型的基础。
Exception —— 面向“常规”错误
Exception 是所有可能在程序中出现并且可以且应该被处理的异常的基类。这些是“工作中的”错误:文件、网络、输入/输出、用户输入等问题。你的大多数 try-catch 都会与 Exception 的子类打交道。
示例:
- IOException —— 处理文件或网络时出错。
- SQLException —— 处理数据库时出错。
- FileNotFoundException —— 找不到文件。
Error —— 用于 JVM 的致命错误
Error 是发生在 Java 虚拟机(JVM)层面的错误的基类。通常这是关键性故障,程序既不能也不应该去处理。如果出现 Error,应用大概率无法继续运行。
示例:
- OutOfMemoryError —— 内存耗尽。
- StackOverflowError —— 栈溢出(例如由于无限递归)。
- NoClassDefFoundError —— 找不到所需的类。
重要:
去捕获并处理 Error 几乎总是个坏主意。这不是你的程序的问题,而是运行环境的故障。
2. Checked vs Unchecked 异常:这是什么意思?
在 Java 中,所有异常分为两大类:
受检(Checked)异常
这是什么? 编译器强制你处理或显式向上抛出的异常。
何时出现? 通常与外部资源相关:文件、网络、数据库、用户输入。
如何处理? 要么用 try-catch 包裹,要么在方法签名中添加 throws。
示例: IOException, SQLException, FileNotFoundException
示例:
public void readFile(String path) throws IOException {
FileReader reader = new FileReader(path); // 可能抛出 IOException
// ...
}
如果既不处理也不向外抛出——程序将无法编译!
非受检(Unchecked)异常
这是什么? 编译器不要求你必须处理的异常。
何时出现? 通常是程序逻辑错误:除以零、数组越界、访问 null 等。
如何处理? 可以捕获,但非必需。更好的做法是通过前置检查来避免这类错误。
在层次结构中的位置? 都继承自 RuntimeException。
示例: NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException, ArithmeticException
示例:
int[] arr = {1, 2, 3};
System.out.println(arr[10]); // ArrayIndexOutOfBoundsException
编译器不会强制你捕获该异常——但一旦出错,程序会崩溃。
3. 一图看懂整个层次结构
graph TD
Throwable --> Error
Throwable --> Exception
Exception --> RuntimeException
Exception --> CheckedExceptions["(其他 Checked Exceptions)"]
Error --> OutOfMemoryError
Error --> StackOverflowError
RuntimeException --> NullPointerException
RuntimeException --> IndexOutOfBoundsException
RuntimeException --> IllegalArgumentException
%% 样式
style Throwable fill:#ffa64d,color:#000
style Exception fill:#ffa64d,color:#000
style CheckedExceptions fill:#ffa64d,color:#000
style Error fill:#ff4d4d,color:#fff
style OutOfMemoryError fill:#ff4d4d,color:#fff
style StackOverflowError fill:#ff4d4d,color:#fff
style RuntimeException fill:#4dff88,color:#000
style NullPointerException fill:#4dff88,color:#000
style IndexOutOfBoundsException fill:#4dff88,color:#000
style IllegalArgumentException fill:#4dff88,color:#000
表格:主要差异
| 类别 | 父类 | 需要处理吗? | 示例 |
|---|---|---|---|
| Checked Exception | |
是 | |
| Unchecked | |
否 | |
| Error | |
否 | |
4. 代码中是什么样的?
受检异常:文件示例
import java.io.*;
public class FileDemo {
public static void main(String[] args) {
try {
FileReader reader = new FileReader("nofile.txt"); // FileNotFoundException (checked)
int data = reader.read();
System.out.println(data);
reader.close();
} catch (IOException e) {
System.out.println("处理文件时出错: " + e.getMessage());
}
}
}
编译器会强制你处理 IOException!
非受检异常:除以零示例
public class ExceptionDemo {
public static void main(String[] args) {
int a = 10;
int b = 0;
int c = a / b; // ArithmeticException (unchecked)
System.out.println("结果: " + c);
}
}
编译器不要求处理,但程序会“崩溃”。
5. 为什么需要异常层次结构?
- 处理的灵活性: 既可以捕获具体错误(FileNotFoundException),也可以捕获一类错误(IOException 或 Exception)。
- 代码复用: 可以对同一类型的错误进行集中处理。
- 代码整洁: 主体逻辑不会被各种零碎检查所污染。
示例:
try {
// 危险代码
} catch (FileNotFoundException e) {
System.out.println("文件未找到!");
} catch (IOException e) {
System.out.println("输入/输出错误!");
} catch (Exception e) {
System.out.println("出现了问题: " + e.getMessage());
}
6. 处理异常时的常见错误
错误 № 1:忽略异常。 写 catch (Exception e) {} —— 很糟糕!你会丢失错误原因的信息。
错误 № 2:捕获过多。 catch (Exception e) 会把所有东西都抓住,甚至包括未预期的。最好只捕获你能够处理的异常。
错误 № 3:去捕获(Error)。 除非你在写低层代码,否则不要捕获 Error。这是 JVM 的问题,而不是你的程序的问题。
错误 № 4:不区分受检与非受检。 并非所有异常都一样!受检需要处理(Exception),非受检则不需要(RuntimeException 及其子类)。
错误 № 5:不给异常添加信息。 如果你创建自定义异常——务必添加有信息量的消息。
GO TO FULL VERSION