前言

"很多人说自己会 Java 8,其实只是会写几个 stream()。"

Java 8 真正带来的变化,不只是多了几个 API,而是把整个编码风格往函数式、声明式和不可变方向推了一大步。

这一篇不追求大而全,只讲最该掌握的 5 个核心能力:Lambda、Stream、方法引用、Optional 和 DateTime API。把这几个点吃透,你的 Java 代码风格会明显上一个台阶。


一、Lambda 表达式:函数式编程的起点

Lambda 是 Java 8 最关键的起点,也是后面 Stream、Optional 等特性的基础。

Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
};

Runnable r2 = () -> System.out.println("Hello");

实战里最常见的价值,不是“少写几行”,而是把行为当参数传进去。

new LambdaQueryWrapper<SysUserEntity>()
    .eq(StringUtils.hasText(status), SysUserEntity::getStatus, status)
    .eq(SysUserEntity::getDeleted, Boolean.FALSE)
    .and(StringUtils.hasText(keyword), wrapper -> wrapper
        .like(SysUserEntity::getUsername, keyword)
        .or()
        .like(SysUserEntity::getDisplayName, keyword)
        .or()
        .like(SysUserEntity::getMobile, keyword))
    .orderByDesc(SysUserEntity::getId);

注意点

  • Lambda 捕获的局部变量必须是 effectively final
  • 参数类型通常可以省略,由编译器推断
  • 单表达式 Lambda 可以省略大括号和 return

二、Stream API:让集合操作更像“描述意图”

Stream 最重要的价值,不是炫技,而是把“做什么”从“怎么做”里拆出来。

常见操作

操作类型方法说明
中间操作filter()过滤元素
中间操作map()元素转换
中间操作flatMap()展平嵌套集合
中间操作distinct()去重
中间操作sorted()排序
终止操作collect()收集结果
终止操作findFirst()取第一个元素
终止操作toList()收集为列表,Java 16+

1. 对象转换

List<ChatSessionResponse> responses = page.getRecords().stream()
    .map(this::toSessionResponse)
    .toList();

如果还在 Java 8 环境,可以写成:

List<ChatSessionResponse> responses = page.getRecords().stream()
    .map(this::toSessionResponse)
    .collect(Collectors.toList());

2. 转成 Map

Map<Long, ChunkEntity> chunkMap = chunkMapper.selectBatchIds(chunkIds)
    .stream()
    .collect(Collectors.toMap(
        ChunkEntity::getId,
        Function.identity()
    ));

如果 key 可能重复,要显式写合并逻辑:

.collect(Collectors.toMap(
    Entity::getId,
    Function.identity(),
    (existing, replacement) -> existing
));

3. 分组聚合

Map<Long, List<String>> capabilitiesByModel = capabilities.stream()
    .collect(Collectors.groupingBy(
        AiModelCapabilityEntity::getModelId,
        Collectors.mapping(
            AiModelCapabilityEntity::getCapabilityType,
            Collectors.toList()
        )
    ));

4. flatMap 展平

List<Long> chunkIds = refsByMessageId.values().stream()
    .flatMap(List::stream)
    .map(ChatAnswerReferenceEntity::getChunkId)
    .toList();

5. 统计计算

long totalPromptTokens = messages.stream()
    .map(ChatMessageEntity::getPromptTokens)
    .filter(Objects::nonNull)
    .mapToLong(Integer::longValue)
    .sum();

Stream 使用建议

  • 不要把 Stream 当成“链式炫技工具”
  • 避免在 Lambda 中修改外部状态
  • parallelStream() 不是禁用项,但不要默认觉得它更快,必须按场景评估
  • 一个 Stream 只能消费一次

三、方法引用:能省则省,但别写错

方法引用是 Lambda 的语法糖,本质没变,只是更简洁。

类型语法示例
静态方法引用ClassName::staticMethodObjects::nonNull
特定对象实例方法引用instance::methodthis::toSessionResponse
任意对象实例方法引用ClassName::methodString::toUpperCase
构造方法引用ClassName::newArrayList::new
.filter(Objects::nonNull)
.map(this::toSessionResponse)
.map(ChatMessageEntity::getId)

真正的构造方法引用应该长这样:

Supplier<List<String>> supplier = ArrayList::new;

Function.identity() 不是构造方法引用,它只是“返回参数本身”的函数。


四、Optional:少写空指针判断,但别滥用

Optional 是 Java 8 引入的容器类,用于表达“这个值可能不存在”。

Optional<String> opt1 = Optional.of("hello");
Optional<String> opt2 = Optional.ofNullable(maybeNull);
Optional<String> opt3 = Optional.empty();

常见写法:

String value = opt.orElse("默认值");

String value2 = opt.orElseGet(this::computeDefaultValue);

String value3 = opt.orElseThrow(() -> new BusinessException("值不存在"));

ifPresentOrElse() 也很好用,但这里要注意版本:

opt.ifPresentOrElse(
    value -> System.out.println("值: " + value),
    () -> System.out.println("无值")
);

这个方法是 Java 9 才加的,不是 Java 8。

Stream 配合 Optional 的典型用法:

public EmbeddingModelClient getClient(String providerCode) {
    return clients.stream()
        .filter(client -> client.supports(providerCode))
        .findFirst()
        .orElseThrow(() -> new BusinessException("当前未实现该模型提供方的客户端"));
}

Optional 注意点

  • 适合做返回值,不适合做字段或方法参数
  • 尽量不要直接调用 get()
  • 不要为了“显得高级”到处包一层 Optional

五、DateTime API:真正该淘汰的是旧时间 API

java.time 是 Java 8 最应该全面拥抱的一套 API。

用途
LocalDate日期
LocalTime时间
LocalDateTime日期时间
InstantUTC 时间点
Duration时间段
ZonedDateTime带时区时间

实战里最常见的写法:

LocalDateTime now = LocalDateTime.now();
Instant start = Instant.now();

int latencyMs = (int) Duration.between(start, Instant.now()).toMillis();

LocalDateTime staleBefore = LocalDateTime.now().minus(Duration.ofMinutes(5));

String datePath = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);

配合 Spring 或 Redis 也很自然:

Duration ttl = Duration.ofSeconds(refreshTokenTtlSeconds);
stringRedisTemplate.opsForValue().set(key, value, ttl);

DateTime API 注意点

  • 新项目尽量不要继续扩散 Date/Calendar
  • 涉及时区时,优先明确使用 InstantZonedDateTime
  • 数据库存储和序列化时要确认时区策略一致

总结

Java 8 最核心的升级,不是某个单独 API,而是编码思维的变化:

  1. Lambda 让行为可传递
  2. Stream 让集合操作更声明式
  3. 方法引用让样板代码更少
  4. Optional 让空值表达更明确
  5. DateTime API 让时间处理终于变得可靠

如果你想真正跨过“会写 Java”和“会写现代 Java”的分界线,这 5 个点必须熟。


欢迎关注公众号 FishTech Notes,一块交流使用心得!

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com