Java 自 2018 年采用六个月发版节奏以来,语言特性演进速度显著加快。从 Java 8 的 Lambda 与 Stream,到 Java 17 的正式版 LTS,再到 Java 21 的虚拟线程与结构化并发,现代 Java 已不再是"冗长"的代名词。本文以 Java 17/21 为基线,系统梳理核心语法、面向对象设计、函数式编程、异常与并发处理、Stream 高阶用法、集合新 API 以及模块系统 JPMS,力求为日常工程实践提供可直接落地的深度参考。
一、Java 17/21 现代语法特性
Java 17 是继 Java 11 之后的又一个长期支持(LTS)版本,引入了多项的生产级语法。Java 21 在此基础上进一步扩展了模式匹配的覆盖面。以下逐一解析最实用的特性。
1.1 Record — 数据载体类的终极简化
Record 在 Java 16 中正式定稿,专门用于声明不可变的数据传输对象(DTO)。编译器会自动生成构造函数、访问器、equals、hashCode 和 toString。
// 声明一个表示二维点的 Record
public record Point(int x, int y) {}
// 使用方式与常规类一致
public class RecordDemo {
public static void main(String[] args) {
Point p1 = new Point(3, 4);
Point p2 = new Point(3, 4);
// 自动生成的访问器,方法名与字段同名
System.out.println(p1.x()); // 输出: 3
System.out.println(p1.y()); // 输出: 4
// 自动生成的 equals 基于所有组件
System.out.println(p1.equals(p2)); // 输出: true
// 自动生成的 toString
System.out.println(p1); // 输出: Point[x=3, y=4]
}
}
Record 可以声明额外的方法和静态字段,但实例字段只能在头部定义。它也支持自定义构造函数进行校验,这种构造函数称为紧凑构造函数。
public record User(String name, int age) {
// 紧凑构造函数:无需参数列表,自动访问组件
public User {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("姓名不能为空");
}
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
}
// 额外方法
public boolean isAdult() {
return age >= 18;
}
// 静态字段
public static final User ANONYMOUS = new User("匿名", 0);
}
Record 的语义约束必须牢记:实例状态不可变,因此不能继承其他类(隐式继承 java.lang.Record),也不能声明实例变量。
1.2 密封类(Sealed Class)— 可控继承层次
Java 17 正式引入密封类,允许类的作者显式声明哪些类或接口可以继承/实现它。这在设计领域模型、构建安全的类型层次时极为有用。
// 使用 sealed + permits 关键字限定子类
public abstract sealed class Payment
permits CreditCardPayment, AliPayPayment, WeChatPayment {}
// 子类必须是 final, sealed 或 non-sealed 中的一种
public final class CreditCardPayment extends Payment {
private final String cardNumber;
public CreditCardPayment(String cardNumber) { this.cardNumber = cardNumber; }
}
public final class AliPayPayment extends Payment {
private final String accountId;
public AliPayPayment(String accountId) { this.accountId = accountId; }
}
public non-sealed class WeChatPayment extends Payment {
// non-sealed 表示解除密封,允许进一步扩展
}
密封类与 Record 结合使用,可以定义清晰、穷举的代数数据类型(ADT),这是函数式编程风格的基石。
public sealed interface Result<T>
permits Success, Failure {}
public record Success<T>(T value) implements Result<T> {}
public record Failure<T>(Exception error) implements Result<T> {}
1.3 模式匹配 — instanceof 与 switch 的革命
Java 16 引入了 instanceof 的模式匹配,Java 17 预览、Java 21 正式定稿了 Switch 的模式匹配。这两项特性极大降低了类型判断与转换的样板代码。
// Java 16 之前:先判断,再强制转换
void oldStyle(Object obj) {
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.toUpperCase());
}
}
// Java 16 起:模式匹配自动绑定变量
void newStyle(Object obj) {
if (obj instanceof String s) {
System.out.println(s.toUpperCase()); // s 直接可用
}
}
Java 21 的 Switch 模式匹配允许在 case 标签中直接绑定变量,并支持守卫条件(guard patterns):
public String classifyShape(Shape shape) {
return switch (shape) {
case Circle c when c.radius() > 10 -> "大圆";
case Circle c -> "小圆";
case Rectangle r when r.width() == r.height() -> "正方形";
case Rectangle r -> "长方形";
case null -> "空对象";
default -> "未知形状";
};
}
1.4 var 局部变量类型推断
var 在 Java 10 中引入,Java 11 增加了 Lambda 参数的局部类型推断。它仅适用于局部变量,要求编译器能从右侧表达式推断类型,并非"动态类型"。
// 合法用法
var list = new ArrayList<String>(); // 推断为 ArrayList<String>
var stream = list.stream(); // 推断为 Stream<String>
var mapper = (Map<String, Integer> m) -> m.size(); // Lambda 参数推断
// 非法用法 — 无法推断或语义模糊
// var x = null; // 编译错误
// var a = 1, b = "two"; // 编译错误:不能用于复合声明
var 提升了代码可读性,尤其在泛型嵌套较深时。但应避免在字面量类型不明显(如 var x = 1 到底是 int 还是 long)的场合滥用。
1.5 文本块(Text Blocks)— 多行字符串原语
Java 15 正式定稿的文本块使用三引号 """ 包裹,自动处理缩进与换行,彻底终结了 SQL、JSON、HTML 拼接的转义噩梦。
public class TextBlockDemo {
// 传统字符串拼接
String oldJson = "{\n" +
" \"name\": \"Alice\",\n" +
" \"age\": 30\n" +
"}";
// 文本块 — 清晰、无需转义引号
String newJson = """
{
"name": "Alice",
"age": 30
}
""";
// 文本块支持格式化方法 formatted()
String template = """
SELECT id, name, email
FROM users
WHERE status = '%s'
ORDER BY created_at DESC
""".formatted("ACTIVE");
}
文本块末尾的三引号位置决定了前导空白裁剪量。\s 可保留行尾空格,\ 可取消换行实现行连接。
二、面向对象深度设计
Java 的面向对象体系在近几个版本中经历了重大演变:接口可以包含默认方法,函数式接口成为 Lambda 的基石,“组合优于继承"的理念也越发被重视。
2.1 继承 vs 组合 — 从脆弱基类到行为委托
继承的风险在于"脆弱基类问题”:父类的任何修改都可能破坏子类假设。Effective Java 中反复强调,当 B 确实是一种 A(IS-A 关系)时才使用继承,否则应优先选择组合。
// 继承 — 紧耦合,风险高
public class MyHashSet<E> extends HashSet<E> {
private int addCount = 0;
@Override
public boolean add(E e) {
addCount++; // 脆弱的覆盖:addAll 内部调用 add,行为不可预测
return super.add(e);
}
}
// 组合 — 通过转发类实现,更灵活、更安全
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) { this.s = s; }
public int size() { return s.size(); }
public boolean isEmpty() { return s.isEmpty(); }
public boolean contains(Object o) { return s.contains(o); }
public boolean add(E e) { return s.add(e); }
// ... 其余方法全部委托给 s
public Iterator<E> iterator() { return s.iterator(); }
public Object[] toArray() { return s.toArray(); }
public <T> T[] toArray(T[] a) { return s.toArray(a); }
public boolean remove(Object o) { return s.remove(o); }
public boolean containsAll(Collection<?> c) { return s.containsAll(c); }
public boolean addAll(Collection<? extends E> c) { return s.addAll(c); }
public boolean removeAll(Collection<?> c) { return s.removeAll(c); }
public boolean retainAll(Collection<?> c) { return s.retainAll(c); }
public void clear() { s.clear(); }
}
// 基于组合的装饰器模式实现
public class InstrumentedSet<E> extends ForwardingSet<E> {
private int addCount = 0;
public InstrumentedSet(Set<E> s) { super(s); }
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() { return addCount; }
}
2.2 接口演变 — 默认方法与静态方法
Java 8 允许接口使用 default 提供默认实现,static 提供工具方法。这一设计让接口可以在不破坏现有实现的前提下扩展功能。
public interface Cache<K, V> {
V get(K key);
void put(K key, V value);
void remove(K key);
// 默认方法:所有实现自动获得该行为
default boolean containsKey(K key) {
return get(key) != null;
}
// 静态方法:属于接口本身的工厂或工具
static <K, V> Cache<K, V> noOp() {
return new Cache<>() {
public V get(K key) { return null; }
public void put(K key, V value) {}
public void remove(K key) {}
};
}
}
当一个类实现多个接口且接口间存在签名相同的默认方法时,编译器强制要求显式覆盖解决冲突,这被称为"菱形继承问题"的现代解法。
2.3 函数式接口 — Lambda 的语法粘合剂
函数式接口(Functional Interface)是仅含一个抽象方法的接口。Java 8 在 java.util.function 包中提供了一系列标准函数式接口。
| 接口 | 方法签名 | 用途 |
|---|---|---|
Predicate<T> | boolean test(T t) | 断言判断,过滤条件 |
Function<T,R> | R apply(T t) | 类型转换,映射逻辑 |
Consumer<T> | void accept(T t) | 副作用操作,如打印 |
Supplier<T> | T get() | 工厂、延迟计算 |
BiFunction<T,U,R> | R apply(T t, U u) | 两参数映射,如合并 |
UnaryOperator<T> | T apply(T t) | 一元运算,同入同出 |
import java.util.function.*;
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
// Predicate:判断整数是否为偶数
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // true
// Function:字符串转长度
Function<String, Integer> lengthFunc = String::length;
System.out.println(lengthFunc.apply("hello")); // 5
// Consumer:打印日志
Consumer<String> logger = msg -> System.out.println("[LOG] " + msg);
logger.accept("系统启动完成");
// Supplier:延迟创建对象
Supplier<List<String>> listFactory = ArrayList::new;
List<String> list = listFactory.get();
// 组合操作:andThen, compose
Function<Integer, Integer> multiplyBy2 = x -> x * 2;
Function<Integer, String> toString = x -> "结果: " + x;
Function<Integer, String> combined = multiplyBy2.andThen(toString);
System.out.println(combined.apply(5)); // "结果: 10"
}
}
自定义函数式接口使用 @FunctionalInterface 注解可以让编译器帮助检查是否满足单抽象方法约束。
@FunctionalInterface
public interface ResultMapper<T, R> {
R map(T input) throws Exception; // 允许声明受检异常
// 默认方法不计入抽象方法计数
default ResultMapper<T, R> andThen(ResultMapper<R, R> after) {
return input -> after.map(this.map(input));
}
}
三、异常处理与异步编程
现代 Java 异常处理已经从简单的 try-catch 演进到声明式资源管理和链式异步计算。
3.1 try-with-resources — 资源管理的最佳实践
Java 7 引入的 try-with-resources 确保所有实现了 AutoCloseable 的资源在使用后自动关闭,消除了 finally 块中手动关闭的冗长与遗漏风险。
import java.io.*;
import java.nio.file.*;
public class ResourceManagement {
// 传统方式 — 易遗漏、代码冗长
public String readOldStyle(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close(); // 重复且易忘
}
}
// try-with-resources — 自动按声明逆序关闭
public String readModern(String path) throws IOException {
try (var br = Files.newBufferedReader(Path.of(path))) {
return br.readLine();
} // br 自动关闭,无需 finally
}
// 多资源同时声明
public void copyFile(String src, String dst) throws IOException {
try (var in = new FileInputStream(src);
var out = new FileOutputStream(dst)) {
in.transferTo(out); // Java 9+ 的便捷方法
}
}
// 自定义 AutoCloseable
public static class DatabaseConnection implements AutoCloseable {
public void execute(String sql) {
System.out.println("执行: " + sql);
}
@Override
public void close() {
System.out.println("数据库连接已关闭");
}
}
public static void main(String[] args) {
try (var conn = new DatabaseConnection()) {
conn.execute("SELECT * FROM users");
} // 自动调用 close()
}
}
Java 9 起支持在 try 语句外声明的 final 或有效 final 资源也可直接引用,提升了灵活性。
3.2 CompletableFuture — 声明式异步编排
CompletableFuture 是 Java 8 引入的非阻塞异步编程工具,支持链式组合、异常恢复与多任务聚合,相比回调地狱是数量级的体验提升。
import java.util.concurrent.*;
import java.util.List;
public class AsyncProgramming {
private static final ExecutorService executor =
Executors.newFixedThreadPool(4);
// 异步获取用户,_then_ 异步获取订单
public CompletableFuture<List<Order>> getUserOrders(long userId) {
return fetchUser(userId)
.thenCompose(user -> fetchOrders(user.id())) // 扁平化嵌套 Future
.exceptionally(ex -> {
System.err.println("获取订单失败: " + ex.getMessage());
return List.of(); // 返回默认值
});
}
// 并行获取用户与订单统计,再合并结果
public CompletableFuture<Dashboard> buildDashboard(long userId) {
CompletableFuture<User> userFuture = fetchUser(userId);
CompletableFuture<Stats> statsFuture = fetchStats(userId);
return userFuture.thenCombine(statsFuture, (user, stats) ->
new Dashboard(user.name(), stats.totalAmount())
);
}
// 聚合多个独立请求
public CompletableFuture<List<Product>> fetchRecommendations() {
CompletableFuture<List<Product>> hot = fetchHotProducts();
CompletableFuture<List<Product>> newArrival = fetchNewArrivals();
CompletableFuture<List<Product>> personalized = fetchPersonalized();
return CompletableFuture.allOf(hot, newArrival, personalized)
.thenApply(v -> {
// allOf 返回 Void,需手动提取结果
List<Product> result = new java.util.ArrayList<>();
result.addAll(hot.join());
result.addAll(newArrival.join());
result.addAll(personalized.join());
return result;
});
}
// 模拟远程调用
private CompletableFuture<User> fetchUser(long id) {
return CompletableFuture.supplyAsync(() -> {
sleep(100);
return new User(id, "用户" + id);
}, executor);
}
private CompletableFuture<List<Order>> fetchOrders(long userId) {
return CompletableFuture.supplyAsync(() -> List.of(), executor);
}
private CompletableFuture<Stats> fetchStats(long userId) {
return CompletableFuture.supplyAsync(() -> new Stats(1000), executor);
}
// 辅助方法与记录
private void sleep(long ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
private record User(long id, String name) {}
private record Order(long id, double amount) {}
private record Stats(double totalAmount) {}
private record Dashboard(String userName, double totalAmount) {}
private record Product(long id, String name) {}
private CompletableFuture<List<Product>> fetchHotProducts() { return CompletableFuture.completedFuture(List.of()); }
private CompletableFuture<List<Product>> fetchNewArrivals() { return CompletableFuture.completedFuture(List.of()); }
private CompletableFuture<List<Product>> fetchPersonalized() { return CompletableFuture.completedFuture(List.of()); }
}
CompletableFuture 常用链式方法语义如下:
| 方法 | 语义 | 使用场景 |
|---|---|---|
thenApply | 对结果做转换 | 映射为另一类型 |
thenAccept | 消费结果,无返回值 | 副作用,如打印/入库 |
thenRun | 不依赖结果,执行动作 | 清理/通知 |
thenCompose | 链接另一个异步操作 | 避免 Future<Future |
thenCombine | 合并两个独立异步结果 | 并行后聚合 |
exceptionally | 异常时返回默认值 | 优雅降级 |
handle | 统一处理正常/异常 | 结果与错误都需要处理 |
四、Stream API 高阶实战
Stream API 是 Java 8 对集合操作最具革命性的改进。掌握高阶用法可以写出兼具性能与可读性的声明式代码。
4.1 创建与中间操作
import java.util.*;
import java.util.stream.*;
public class StreamCreation {
public static void main(String[] args) {
// 从集合创建
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
numbers.stream();
// 从数组创建
Arrays.stream(new int[]{1, 2, 3});
// 从值创建
Stream.of("a", "b", "c");
// 迭代生成
Stream.iterate(0, n -> n + 2).limit(10); // 偶数流
// 生成器
Stream.generate(Math::random).limit(5);
// 从文件行创建
// Files.lines(Path.of("data.txt"));
// IntStream 等原始类型流避免装箱
IntStream.range(1, 100); // [1, 100)
IntStream.rangeClosed(1, 100); // [1, 100]
// 中间操作组合:过滤 + 映射 + 去重 + 排序 + 截断
List<String> result = Stream.of(" Java ", "Python", " java", "Go", "RUST")
.map(String::trim) // 去空白
.map(String::toLowerCase) // 转小写
.filter(s -> s.length() > 2) // 过滤短串
.distinct() // 去重
.sorted(Comparator.reverseOrder()) // 倒序
.limit(10) // 限制数量
.collect(Collectors.toList());
System.out.println(result); // [rust, python, java, go]
}
}
4.2 归约与Collector
reduce 是通用的聚合原语,而 Collectors 工具类封装了常见模式。
import java.util.*;
import java.util.stream.*;
public class StreamReduction {
public static void main(String[] args) {
List<Integer> nums = List.of(1, 2, 3, 4, 5);
// 基础归约
int sum = nums.stream().mapToInt(Integer::intValue).sum();
int max = nums.stream().mapToInt(Integer::intValue).max().orElse(0);
// reduce 三参数版:单位元、累加器、合并器(用于并行流)
int parallelSum = nums.parallelStream()
.reduce(0, Integer::sum, Integer::sum);
// 字符串拼接
String joined = Stream.of("a", "b", "c")
.collect(Collectors.joining(", ", "[", "]")); // [a, b, c]
// 分组 — 按长度分组
Map<Integer, List<String>> byLength = Stream.of("apple", "banana", "pear", "kiwi")
.collect(Collectors.groupingBy(String::length));
// 分区 — 按条件分为两组
Map<Boolean, List<Integer>> partitioned = nums.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// 多级分组与下游收集器
Map<String, Map<Integer, Long>> nested = Stream.of(
new Person("Alice", "Dev", 25),
new Person("Bob", "Dev", 30),
new Person("Carol", "QA", 28)
)
.collect(Collectors.groupingBy(
Person::department,
Collectors.groupingBy(
Person::age,
Collectors.counting()
)
));
// 自定义 Collector:收集为不可变集合
List<String> immutable = Stream.of("a", "b")
.collect(Collectors.collectingAndThen(
Collectors.toList(),
Collections::unmodifiableList
));
}
record Person(String name, String department, int age) {}
}
4.3 并行流 — 性能陷阱与正确使用
并行流底层使用 ForkJoinPool.commonPool(),并非所有场景都能加速。
import java.util.*;
import java.util.stream.*;
public class ParallelStream {
public static void main(String[] args) {
List<Integer> data = new Random()
.ints(1_000_000, 1, 100)
.boxed()
.toList();
// 适合并行:大数据量、无状态、无副作用、计算密集型
long start = System.currentTimeMillis();
long primeCount = data.parallelStream()
.filter(ParallelStream::isPrime)
.count();
System.out.println("并行耗时: " + (System.currentTimeMillis() - start) + "ms");
// 错误示范:在并行流中修改外部变量(线程不安全)
int[] counter = {0};
data.parallelStream().forEach(e -> counter[0]++); // 数据竞争!
// 正确做法:使用归约合并结果
long correctCount = data.parallelStream()
.filter(ParallelStream::isPrime)
.reduce(0L, (c, e) -> isPrime(e) ? c + 1 : c, Long::sum); // 不,这里用 count() 即可
}
static boolean isPrime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
}
五、集合框架新 API
Java 8 以降的集合 API 显著增强,新增了工厂方法、便利操作与不可变集合支持。
5.1 List/Set/Map 工厂方法
import java.util.*;
public class CollectionFactories {
public static void main(String[] args) {
// Java 9 起的不变集合工厂方法
List<String> list = List.of("a", "b", "c");
Set<Integer> set = Set.of(1, 2, 3);
Map<String, Integer> map = Map.of("Alice", 25, "Bob", 30);
// 超过 10 个条目用 ofEntries
Map<String, Integer> largeMap = Map.ofEntries(
Map.entry("k1", 1),
Map.entry("k2", 2),
Map.entry("k3", 3)
);
// 注意:这些集合不可变,任何修改操作抛出 UnsupportedOperationException
// list.add("d"); // 运行时报错
}
}
5.2 Map 便利操作
Java 8 为 Map 增加了 compute、merge、putIfAbsent 等原子操作方法。
import java.util.*;
public class MapNewApis {
public static void main(String[] args) {
Map<String, List<String>> groups = new HashMap<>();
List<String> data = List.of("apple:fruit", "carrot:vegetable", "banana:fruit");
// 优雅的分组,无需显式判空
for (String item : data) {
String[] parts = item.split(":");
String key = parts[1];
String value = parts[0];
// computeIfAbsent:键不存在时计算值
groups.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
}
// merge:合并新旧值
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 80);
scores.merge("Alice", 10, Integer::sum); // 存在则相加,Alice -> 90
scores.merge("Bob", 50, Integer::sum); // 不存在则直接放入,Bob -> 50
// compute:基于旧值重新计算
scores.compute("Alice", (k, v) -> v == null ? 0 : v * 2); // Alice -> 180
// putIfAbsent / replaceAll / forEach
scores.putIfAbsent("Carol", 60);
scores.replaceAll((k, v) -> v + 5); // 全员加 5 分
scores.forEach((name, score) ->
System.out.println(name + ": " + score)
);
// getOrDefault:安全取值
int daveScore = scores.getOrDefault("Dave", 0);
}
}
5.3 Collectors + 不可变集合
import java.util.*;
import java.util.stream.*;
public class ImmutableCollections {
public static void main(String[] args) {
List<String> source = List.of("apple", "banana", "apricot", "blueberry", "cherry");
// JDK 10+ 流收集为不可变集合
List<String> immutableList = source.stream()
.filter(s -> s.startsWith("a"))
.collect(Collectors.toUnmodifiableList());
Set<String> immutableSet = source.stream()
.map(String::toUpperCase)
.collect(Collectors.toUnmodifiableSet());
Map<Character, List<String>> immutableMap = source.stream()
.collect(Collectors.groupingBy(
s -> s.charAt(0),
Collectors.toUnmodifiableList()
));
// Java 16+ Stream.toList() 更简洁(返回不可变列表)
List<String> concise = source.stream()
.filter(s -> s.length() > 5)
.toList(); // 等同于 collect(Collectors.toUnmodifiableList())
}
}
六、模块系统 JPMS
Java 9 引入的 Java Platform Module System(JPMS)是语言级别的一次结构性变革,旨在解决 JAR 地狱、强封装与依赖显式化问题。
6.1 声明模块
模块通过 module-info.java 声明,位于源码根目录下。
// /src/main/java/module-info.java
module com.example.application {
// 声明本模块依赖的其他模块
requires java.base; // 隐式依赖,可省略
requires java.net.http; // Java 11 的 HTTP Client
requires java.sql;
// 传递依赖:依赖本模块的模块也会自动依赖 transitive 模块
requires transitive com.example.common;
// 静态依赖:仅编译期需要,运行时可选
requires static org.slf4j;
// 暴露指定包给其他模块使用
exports com.example.application.api;
// 定向暴露:仅允许特定模块访问
exports com.example.application.internal to com.example.test;
// 开放包给反射使用(如 Spring/ORM)
opens com.example.application.entity;
// 使用某服务接口
uses com.example.spi.NotificationService;
// 提供服务实现
provides com.example.spi.NotificationService
with com.example.application.EmailNotificationService;
}
6.2 服务加载器模式
JPMS 内置了 ServiceLoader 机制,可实现接口与实现的解耦。
// 服务接口模块 com.example.spi
package com.example.spi;
public interface NotificationService {
void send(String userId, String message);
}
// 服务提供模块 com.example.email
module com.example.email {
requires com.example.spi;
provides com.example.spi.NotificationService
with com.example.email.EmailNotificationService;
}
// 服务消费方模块
public class NotificationClient {
public void notifyAllUsers(String message) {
// ServiceLoader 自动发现模块路径上的所有实现
ServiceLoader<NotificationService> loader =
ServiceLoader.load(NotificationService.class);
for (NotificationService service : loader) {
System.out.println("使用服务: " + service.getClass().getName());
// service.send(...);
}
}
}
6.3 未命名模块与迁移路径
现有未模块化代码在 JPMS 中以"未命名模块"(unnamed module)运行,可读取所有模块并导出所有包,保障了平滑迁移。
# 保留经典类路径运行
java -cp "lib/*" com.example.LegacyApp
# 混合路径:模块化应用依赖未命名 JAR
java -p modules -cp "legacy/*" -m com.example.application/com.example.Main
七、最佳实践
7.1 API 设计:优先返回接口,隐藏实现
// 推荐:返回 List 接口,方便日后更换实现
public List<User> findActiveUsers() {
return Collections.unmodifiableList(internalList);
}
// 避免:暴露具体实现类
// public ArrayList<User> findActiveUsers() { ... }
7.2 防御性拷贝与不可变对象
public class Period {
private final LocalDate start;
private final LocalDate end;
public Period(LocalDate start, LocalDate end) {
// 防御性拷贝防止外部修改
this.start = Objects.requireNonNull(start);
this.end = Objects.requireNonNull(end);
if (start.isAfter(end)) {
throw new IllegalArgumentException("开始日期不能晚于结束日期");
}
}
// 访问器也拷贝(对于可变类型如 Date)
public LocalDate start() { return start; } // LocalDate 本身不可变,直接返回
}
7.3 Optional 的正确用法
Optional 设计用于返回值,而非字段和参数。
// 推荐:作为返回值表达可能缺失
public Optional<User> findById(long id) { ... }
// 消费时提供默认值
User user = findById(1L).orElse(User.guest());
// 或抛出业务异常
User user = findById(1L).orElseThrow(() -> new NotFoundException("用户不存在"));
// 避免:字段声明 — 增加内存开销且难以序列化
// private Optional<String> nickname; // 不推荐
7.4 方法引用优先级
在 Lambda 逻辑只是简单委托时,优先使用方法引用,语义更简洁。
// 不建议:冗余的 Lambda
list.stream().map(s -> s.toUpperCase());
list.forEach(x -> System.out.println(x));
// 推荐:方法引用
list.stream().map(String::toUpperCase);
list.forEach(System.out::println);
八、常见问题 FAQ
Q1:Record 可以序列化吗?
A:Record 可以正常实现 java.io.Serializable,且序列化机制会自动基于组件进行读写,无需显式编写 readObject/writeObject。但注意:通过反射修改 Record 字段会导致反序列化失败,因为 Record 的语义完整性要求构造函数参与反序列化流程。
Q2:密封类和普通抽象类的本质区别是什么?
A:密封类通过 sealed/permits 将继承权限收回到声明者手中,编译器与 JVM 均可验证子类集合的完整性(exhaustiveness)。这直接支撑了 Switch 模式匹配的穷举检查:如果密封类的所有子类都在 switch 中覆盖,编译器允许省略 default 分支。
Q3:Stream 中 forEach 与 for 循环怎么选?
A:若操作涉及外部状态修改或有副作用,for 循环更清晰;若进行纯函数式转换、过滤、聚合,则 Stream 链式操作更具表达力。切忌在 Stream 中通过 forEach 做外部累加,这破坏了函数式契约且线程不安全。
Q4:JPMS 下反射访问私有成员失败怎么办?
A:JPMS 强封装了未导出的包,反射访问会抛出 InaccessibleObjectException。解决方案包括:(1) 在 module-info.java 中 opens 对应包;(2) 启动时加 --add-opens module/package=targetModule;(3) 尽量避免跨模块的深度反射。
Q5:CompletableFuture 默认线程池不够用怎么处理?
A:CompletableFuture 的异步方法(如 supplyAsync 无指定 Executor 版本)使用 ForkJoinPool.commonPool(),其大小默认为 CPU 核心数 - 1。对于 IO 密集型任务,务必显式传入自定义 ExecutorService,独立管理线程生命周期,防止阻塞 common pool 影响系统级并行流操作。
结语
从 Java 8 到 Java 21,语言特性的演进始终围绕一个主题:减少模板代码,提升表达力与安全性。Record 消除了 DTO 的样板;密封类让类型层次可控;模式匹配解放了类型判断的冗余;Stream 与函数式接口赋予了集合操作声明式语义;CompletableFuture 将异步编程从回调地狱拉回线性逻辑;JPMS 则为大型系统的依赖与封装提供了地基。掌握这些特性,不仅仅是了解语法本身,更是在工程层面建立现代 Java 的设计直觉。
参考版本:Java 17 LTS / Java 21 LTS
建议配合 Java 21 虚拟线程指南 与 Project Panama 外部函数 继续深入。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。