第 7 章 · 异常处理与 IO 流
本章目标:掌握 try-catch-finally 与 try-with-resources;理解受检异常与未受检异常;能定义自定义异常;熟练使用 File、BufferedReader/BufferedWriter 读写文本;完成 toolkit-demo 的配置文件读取与日志行解析;对照 python-dev 的 try/except 与 open()。
学时建议:4~5 小时(含 2 小时跟练)
前置:完成 java-dev ch06;建议已学 python-dev 中异常与文件读写章节。
7.1 场景说明:配置与日志
toolkit-demo 启动时需读取虚构配置文件 app.properties(键值对),并解析 access.log 中的访问行;格式错误时抛出自定义异常,避免静默失败。
learn-java/toolkit-demo/
├── config/
│ └── app.properties # 教学虚构配置
├── data/
│ └── access.log
└── src/main/java/demo/io/
├── ConfigLoader.java
├── LogParser.java
├── ToolkitConfigException.java
└── IoDemo.java
示例 app.properties:
app.name=toolkit-demo
app.version=1.0
pricing.member.rate=0.90
示例 data/access.log(与 §7.7 LogParser 格式一致:时间|路径|状态码):
2026-08-19T10:00:01Z|/books|200
2026-08-19T10:00:02Z|/api/cart|404
2026-08-19T10:00:03Z|/checkout|500
2026-08-19T10:00:04Z|/books|200
2026-08-19T10:00:05Z|/login|302
ch09/ch17 毕业项目会使用更详细的日志格式(含 ms= 耗时);本章先掌握 管道分隔 基础解析,后续章节会升级格式。
7.2 异常体系
- 受检异常(如
IOException):编译器强制catch或throws - 未受检
RuntimeException:如NullPointerException、IllegalArgumentException
| Python | Java |
|---|---|
raise ValueError | throw new IllegalArgumentException |
except Exception | catch (Exception e) |
7.3 try-catch-finally
public static int parsePort(String text) {
try {
int port = Integer.parseInt(text);
if (port < 1 || port > 65535) throw new IllegalArgumentException("端口 1-65535");
return port;
} catch (NumberFormatException e) {
System.err.println("格式错误:" + e.getMessage());
return -1;
}
}
| 块 | 作用 |
|---|---|
try | 可能抛出异常的代码 |
catch | 捕获并处理 |
finally | 清理资源(现代优先 try-with-resources) |
多 catch:catch (IOException | ToolkitConfigException e)(Java 7+)。
7.4 try-with-resources
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public static void readLines(String path) throws IOException {
try (BufferedReader reader = new BufferedReader(
new FileReader(path, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} // 自动 close,即使异常
}
| Python | Java |
|---|---|
with open(...) as f | try (BufferedReader r = ...) { } |
| 上下文管理器 | AutoCloseable 接口 |
7.5 自定义异常
package demo.io;
/**
* toolkit-demo 配置相关异常
*/
public class ToolkitConfigException extends Exception {
private final String configKey;
public ToolkitConfigException(String message, String configKey) {
super(message);
this.configKey = configKey;
}
public ToolkitConfigException(String message, String configKey, Throwable cause) {
super(message, cause);
this.configKey = configKey;
}
public String getConfigKey() {
return configKey;
}
}
| 选型 | 说明 |
|---|---|
extends Exception | 受检,调用方必须处理 |
extends RuntimeException | 未受检,参数错误等 |
业务配置错误本课程用受检异常。路径使用 Paths.get("config", "app.properties") 等相对路径,运行于 toolkit-demo 根目录。
7.6 ConfigLoader 完整代码
package demo.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
/**
* 简单 properties 风格配置加载(不依赖 java.util.Properties 教学演示)
*/
public class ConfigLoader {
public Map<String, String> load(Path path) throws ToolkitConfigException {
if (!Files.isRegularFile(path)) {
throw new ToolkitConfigException("配置文件不存在", path.toString());
}
Map<String, String> map = new HashMap<>();
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line;
int lineNo = 0;
while ((line = reader.readLine()) != null) {
lineNo++;
line = line.strip();
if (line.isEmpty() || line.startsWith("#")) {
continue;
}
int eq = line.indexOf('=');
if (eq <= 0) {
throw new ToolkitConfigException(
"第 " + lineNo + " 行格式错误(缺少 =)", path.toString()
);
}
String key = line.substring(0, eq).strip();
String value = line.substring(eq + 1).strip();
map.put(key, value);
}
} catch (IOException e) {
throw new ToolkitConfigException("读取失败", path.toString(), e);
}
return map;
}