Java 现代语法特性

要点

Stream 描述数据变换管道,终结操作驱动消费;Optional 表达可能缺失;虚拟线程影响任务执行方式。一次只用一个特性解决具体问题。

旧回顾:Java 8 是分水岭

Java 8(2014)引入的 Lambda/Stream/Optional 奠定了”现代 Java”的地基;Java 17/21/25 的 Record/Sealed/模式匹配/虚拟线程则是第二波范式升级。本文按依赖顺序逐个击破。

一、Lambda 与函数式接口

// 旧写法:匿名内部类
Comparator<String> byLenOld = new Comparator<>() {
    @Override
    public int compare(String a, String b) {
        return Integer.compare(a.length(), b.length());
    }
};

// 现代写法:Lambda
Comparator<String> byLen = (a, b) -> Integer.compare(a.length(), b.length());

// 更进一步:方法引用
Comparator<String> byLen2 = Comparator.comparing(String::length);

核心函数式接口(java.util.function):

接口签名用途
Function<T,R>T -> R转换
Predicate<T>T -> boolean判断
Consumer<T>T -> void消费
Supplier<T>() -> T生产

任何”单抽象方法接口”(如 Runnable、Comparator)都能接 Lambda;自己定义时加 @FunctionalInterface 让编译器把关。

二、Stream:声明式数据处理

// 需求:找出所有在库图书的书名,按出版年排序,取前 5 本
List<String> titles = books.stream()                 // 1. 数据源
        .filter(Book::available)                     // 2. 中间操作:过滤
        .sorted(Comparator.comparing(Book::year).reversed())
        .limit(5)
        .map(Book::title)                            // 3. 映射
        .toList();                                   // 4. 终止操作(Java 16+)

要点:

  • 中间操作惰性求值,遇到终止操作才执行;流只能消费一次
  • 常用收集器:Collectors.groupingBy(分组)、toMap、joining、partitioningBy
  • 数值流:mapToInt(...).sum()/average()/summaryStatistics()
  • 完整 API 详见 Stream/Optional 速查

心智模型:循环描述”怎么一步步做”(how),Stream 描述”要什么结果”(what)。简单遍历保留 for,复杂转换/聚合交给 Stream。

三、Optional:让”可能没有”成为类型的一部分

// 旧写法:返回 null,把 NPE 风险转嫁给调用方
public Book findByIsbn(String isbn) { ... /* 可能返回 null */ }

// 现代写法:签名即文档
public Optional<Book> findByIsbn(String isbn) { ... }
bookService.findByIsbn("978-7-111")
        .map(Book::title)
        .filter(t -> !t.isBlank())
        .ifPresentOrElse(
                System.out::println,
                () -> System.out.println("未找到图书"));

使用铁律: – ✅ 方法返回值表达”可能缺失”;配合 orElse/orElseThrow/ifPresent 消费 – ❌ 不用作字段、方法参数、集合元素;不要 get() 裸取值;不要返回 null 的 Optional

四、虚拟线程(Java 21 LTS)

解决的问题:传统平台线程 1:1 映射 OS 线程,几千个阻塞 IO 请求就要几千个 OS 线程,内存与调度成本高。

// 旧写法:线程池有界,IO 等待期间线程被白白占用
ExecutorService pool = Executors.newFixedThreadPool(200);

// 现代写法:每个任务一个虚拟线程,阻塞成本极低
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10_000).forEach(i ->
            executor.submit(() -> {
                Thread.sleep(Duration.ofSeconds(1));   // 模拟 IO 等待
                return fetchRemote(i);
            }));
}   // try-with-resources 等待全部任务完成

使用要点: – 适合IO 密集(HTTP 调用、数据库访问、文件读写);CPU 密集任务用它无收益 – 不要池化虚拟线程——”一任务一线程”就是它的正确用法 – 同步阻塞风格的代码即可获得高并发,无需响应式编程的回调地狱 – Java 21 中 synchronized 块内阻塞会造成”钉住”(pinning),Java 24 已修复;21 上热点路径建议改用 ReentrantLock – 更多细节见并发 API 速查

五、Sealed 类:可控的继承体系

旧问题:interface Shape 谁都能实现,switch 分发永远要 default 兜底,新增实现类不会有任何编译期提醒。

public sealed interface Shape permits Circle, Rectangle, Triangle {}
public record Circle(double radius) implements Shape {}
public record Rectangle(double w, double h) implements Shape {}
public final class Triangle implements Shape {}

子类必须选择一个修饰符收口: – final:不再被继承(record 天然 final) – sealed:继续限定下一层 – non-sealed:重新开放给任意继承

价值在组合:sealed + record + switch 模式匹配 = 编译器保证穷举的代数数据类型,配合示例见控制流程与 Record/Sealed/模式匹配参考。

可复现示例:一条小输入的现代特性闭环(JDK 21)

下例刻意使用很小、固定的输入。它不把 Stream、Optional、虚拟线程和 sealed 类型堆成生产模板,而是分别显示它们可观察的契约:Stream 的过滤与映射结果、Optional 的缺失分支、虚拟线程任务的返回值,以及穷尽 switch 的类型分派。

import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executors;

public class ModernFeatures {
    record Person(String name, int age) {}

    sealed interface Result permits Value, Missing {}
    record Value(int number) implements Result {}
    record Missing() implements Result {}

    static String describe(Result result) {
        return switch (result) {
            case Value(int number) -> "value=" + number;
            case Missing ignored -> "missing";
        };
    }

    public static void main(String[] args) throws Exception {
        var adults = List.of(new Person("Ada", 20), new Person("Bo", 16)).stream()
                .filter(person -> person.age() >= 18)
                .map(Person::name)
                .toList();
        System.out.println(adults);

        System.out.println(Optional.<String>empty().orElse("missing"));

        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            System.out.println("virtual=" + executor.submit(() -> 7).get());
        }

        System.out.println(describe(new Value(3)));
    }
}

try 退出时等待已提交任务完成,因此本例可以安全读取 Future.get() 的结果;真实 IO 任务仍需设置超时、取消和下游连接并发上限。

最佳实践 / 陷阱清单

Stream 适合清晰的变换、筛选与聚合;包含多步副作用或复杂退出逻辑时,普通循环往往更容易验证。Optional 表达单个结果可能缺失,空集合已经能表达“零项”,无需为一致形式层层包装。

虚拟线程降低某些阻塞等待的线程成本,不增加 CPU 算力,也不扩大数据库容量。并行流和虚拟线程都需要资源上限与实际基准,不能仅按“现代特性”一律开启。

练习与实践

练习一:循环改造

  1. 写一段”过滤+分组+计数”的循环代码,用 Stream 重写并对比可读性

练习二:Optional 契约

  1. 把一个返回 null 的方法改为返回 Optional,更新全部调用点

练习三:虚拟线程压测

  1. 用虚拟线程并发模拟 1000 个”睡眠 1 秒”的 IO 任务,观察总耗时
  2. 换成固定线程池(200)对比,体会差异

相关文档

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇