异常是 Java 错误处理的主线;文件 IO 是读配置、日志、导入导出的基础。
1. Throwable 体系
概念
Throwable
├── Error JVM 级严重问题(一般不捕获)
└── Exception
├── RuntimeException(非受检 unchecked)
└── 其他 Exception(受检 checked)
- 受检异常:编译器要求处理或声明
throws(如IOException)。 - 非受检异常:
RuntimeException及子类、Error— 不强制 catch。
原理
异常是对象,抛出时栈回溯 unwinding,直到匹配的 catch 或线程终止。
完整示例
package com.example.demo;
public class ThrowableDemo {
static int parsePort(String s) {
return Integer.parseInt(s); // 可能 NumberFormatException(非受检)
}
public static void main(String[] args) {
try {
System.out.println(parsePort("8080"));
System.out.println(parsePort("abc"));
} catch (NumberFormatException e) {
System.err.println("非法端口: " + e.getMessage());
}
}
}
练习
写方法 divide(int a, int b),除零抛自定义非受检异常 BusinessException(带 message)。
完整例子
package com.example.demo;
class BusinessException extends RuntimeException {
BusinessException(String message) { super(message); }
}
public class DivideDemo {
static int divide(int a, int b) {
if (b == 0) throw new BusinessException("divide by zero");
return a / b;
}
public static void main(String[] args) {
try {
System.out.println(divide(10, 0));
} catch (BusinessException e) {
System.err.println(e.getMessage());
}
}
}
常见坑
- 空
catch吞掉异常 — 至少打日志。 - 用异常控制正常业务流程(如找不到用异常)— 可用 Optional 或返回值。
2. try / catch / finally
概念
try 监控块;catch 按类型匹配(子类在前);finally 几乎总会执行(用于释放资源,现代更推荐 try-with-resources)。
完整示例
package com.example.demo;
public class TryCatchDemo {
static String readSimulated(boolean fail) {
if (fail) throw new IllegalStateException("simulated");
return "ok";
}
public static void main(String[] args) {
String result = "default";
try {
result = readSimulated(true);
} catch (IllegalStateException e) {
result = "fallback";
} finally {
System.out.println("cleanup always runs");
}
System.out.println(result);
}
}
常见坑
return在try与finally都有时,finally 后执行且其return会覆盖 try 的返回值 — 避免 finally 里 return。- 捕获
Exception太宽,掩盖真实问题。
3. try-with-resources
概念
JDK 7+:try (Resource r = ...) { } 自动调用 AutoCloseable.close(),即使抛异常也会关闭(抑制异常机制)。
原理
编译器生成等价于 try-finally 的字节码,且正确处理 primary/suppressed exception。
完整示例
package com.example.demo;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class TryWithResourcesDemo {
static void printFirstLine(Path path) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line = reader.readLine();
System.out.println(line);
}
}
public static void main(String[] args) throws IOException {
Path tmp = Files.createTempFile("demo", ".txt");
Files.writeString(tmp, "hello\nworld", StandardCharsets.UTF_8);
printFirstLine(tmp);
Files.deleteIfExists(tmp);
}
}
练习
读配置文件
要点:路径 config/app.properties;读 app.name、server.port(默认 8080)、feature.enabled;文件不存在友好提示;用 try-with-resources 或 Files.readAllLines。
完整例子
package com.example.demo.exercise;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
public class ReadAppConfigTryWithResources {
public static void main(String[] args) {
Path path = Path.of("config/app.properties");
if (!Files.exists(path)) {
System.err.println("配置文件不存在: " + path.toAbsolutePath());
return;
}
Properties props = new Properties();
// try-with-resources:自动 close Reader
try (Reader r = Files.newBufferedReader(path)) {
props.load(r);
} catch (Exception e) {
System.err.println("读取失败: " + e.getMessage());
return;
}
String name = props.getProperty("app.name", "unknown");
int port = Integer.parseInt(props.getProperty("server.port", "8080"));
boolean enabled = Boolean.parseBoolean(props.getProperty("feature.enabled", "false"));
System.out.printf("app=%s port=%d feature=%s%n", name, port, enabled);
}
}
常见坑
- 资源未实现
AutoCloseable不能放进 try-with-resources。 - 在 try 块外仍使用已关闭资源。
4. throws 与自定义异常
概念
方法签名 throws IOException 把处理责任交给调用方。业务层常用非受检自定义异常 + 全局处理器(Spring @ControllerAdvice)。
完整示例
package com.example.demo;
public class BusinessException extends RuntimeException {
private final String code;
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
public String getCode() { return code; }
}
class OrderService {
void cancel(Long orderId) {
if (orderId == null) {
throw new BusinessException("BAD_REQUEST", "orderId required");
}
// ...
}
}
练习
定义 NotFoundException extends RuntimeException,findUser(id) 找不到时抛出。
完整例子
package com.example.demo.exercise;
import java.util.Map;
class NotFoundException extends RuntimeException {
NotFoundException(String message) { super(message); }
}
public class FindUserDemo {
static final Map<Long, String> DB = Map.of(1L, "alice");
static String findUser(Long id) {
String name = DB.get(id);
if (name == null) {
throw new NotFoundException("user not found: " + id);
}
return name;
}
public static void main(String[] args) {
try {
System.out.println(findUser(99L));
} catch (NotFoundException e) {
System.err.println(e.getMessage());
}
}
}
常见坑
- 受检异常层层
throws导致签名污染 — 业务异常多用 RuntimeException。 - 自定义异常不调用
super(message)导致堆栈/message 丢失。
5. 文件 IO 入门
概念
- NIO.2(
java.nio.file):Path、Files.readString/writeString、linesStream。 - 旧 IO(
java.io.FileInputStream)仍存在,新项目优先 NIO。
原理
读文件 = OS 打开文件描述符 → 读字节 → 解码为 String(指定 Charset,默认 UTF-8 推荐)。
完整示例
package com.example.demo;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Properties;
public class FileIoDemo {
/** 简单 key=value 行配置 */
static Properties loadProperties(Path path) throws IOException {
Properties p = new Properties();
if (!Files.exists(path)) {
return p;
}
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
for (String line : lines) {
line = line.strip();
if (line.isEmpty() || line.startsWith("#")) continue;
int idx = line.indexOf('=');
if (idx > 0) {
p.setProperty(line.substring(0, idx).strip(), line.substring(idx + 1).strip());
}
}
return p;
}
public static void main(String[] args) throws IOException {
Path config = Path.of("app.properties");
Files.writeString(config,
"""
app.name=demo
server.port=8080
# comment
""",
StandardCharsets.UTF_8);
Properties props = loadProperties(config);
System.out.println(props.getProperty("server.port"));
Files.deleteIfExists(config);
}
}
练习
读 config:从 config/app.properties 读取 db.url、db.user,缺键时使用默认值并打印最终配置 Map。
完整例子
package com.example.demo;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class DbConfigDemo {
public static void main(String[] args) throws IOException {
Path path = Path.of("config/app.properties");
Properties props = new Properties();
if (Files.exists(path)) {
try (Reader r = Files.newBufferedReader(path)) {
props.load(r);
}
}
Map<String, String> cfg = new HashMap<>();
cfg.put("db.url", props.getProperty("db.url", "jdbc:h2:mem:demo"));
cfg.put("db.user", props.getProperty("db.user", "sa"));
System.out.println(cfg);
}
}
常见坑
- 未指定编码用平台默认 charset — 跨环境乱码。
- 大文件
readAllLines一次进内存 — 用Files.lines流式处理(见 06 章 Stream)。
6. Properties 与路径
概念
Properties 继承 Hashtable,专门存 String 键值;Spring Boot 用 application.properties / application.yml(后续章节)。
完整示例
package com.example.demo;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
public class PropertiesLoadDemo {
static Properties load(Path path) throws IOException {
Properties props = new Properties();
try (Reader reader = Files.newBufferedReader(path)) {
props.load(reader); // 标准 .properties 格式
}
return props;
}
}
常见坑
- 相对路径相对进程工作目录(IDE Run 的 Working Directory),不是 classpath — Spring 里
@Value读的是 classpath 资源。
本章综合练习:读配置
- 创建
config/app.properties:
app.name=demo
server.port=8080
feature.enabled=true
- 程序读取并输出结构化信息;
server.port缺省为8080。 - 文件不存在时打印友好提示,不崩溃(或抛带说明的 BusinessException)。
提示:exercises.md。
完整例子
package com.example.demo.exercise;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
public class ReadAppConfig {
public static void main(String[] args) {
Path path = Path.of("config/app.properties");
if (!Files.exists(path)) {
System.err.println("配置文件不存在: " + path.toAbsolutePath());
return;
}
Properties props = new Properties();
try (Reader r = Files.newBufferedReader(path)) {
props.load(r);
} catch (Exception e) {
System.err.println("读取失败: " + e.getMessage());
return;
}
String name = props.getProperty("app.name", "unknown");
int port = Integer.parseInt(props.getProperty("server.port", "8080"));
boolean enabled = Boolean.parseBoolean(props.getProperty("feature.enabled", "false"));
System.out.printf("app=%s port=%d feature=%s%n", name, port, enabled);
}
}
本章小结
| 主题 | 要点 |
|---|---|
| 受检 vs 非受检 | IOException vs RuntimeException |
| 资源 | try-with-resources |
| 文件 | Path + Files + UTF-8 |
| 业务 | 自定义 RuntimeException + message/code |