前言
"很多人说自己会 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::staticMethod | Objects::nonNull |
| 特定对象实例方法引用 | instance::method | this::toSessionResponse |
| 任意对象实例方法引用 | ClassName::method | String::toUpperCase |
| 构造方法引用 | ClassName::new | ArrayList::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 | 日期时间 |
Instant | UTC 时间点 |
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 - 涉及时区时,优先明确使用
Instant或ZonedDateTime - 数据库存储和序列化时要确认时区策略一致
总结
Java 8 最核心的升级,不是某个单独 API,而是编码思维的变化:
- Lambda 让行为可传递
- Stream 让集合操作更声明式
- 方法引用让样板代码更少
- Optional 让空值表达更明确
- DateTime API 让时间处理终于变得可靠
如果你想真正跨过“会写 Java”和“会写现代 Java”的分界线,这 5 个点必须熟。
欢迎关注公众号 FishTech Notes,一块交流使用心得!