Java 17+ 核心语法深度指南

Java 17/21 现代核心语法全景:Record、密封类、模式匹配、函数式接口、Stream API 高阶与模块系统,从语法到实战。

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)。编译器会自动生成构造函数、访问器、equalshashCodetoString

// 声明一个表示二维点的 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 增加了 computemergeputIfAbsent 等原子操作方法。

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.javaopens 对应包;(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 外部函数 继续深入。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. Spring Cloud 微服务全栈实践
  2. Spring Security 6.x 与 OAuth2/JWT 安全认证实战
  3. Spring Data JPA 高级指南:关联映射、N+1 与性能优化