
本文介绍一种基于 java 8 stream 的优雅方式,为具有自引用嵌套结构的类(如树形 message 对象)实现深度优先的递归遍历,无需外部依赖,纯 jdk 实现,支持链式调用与函数式组合。
本文介绍一种基于 java 8 stream 的优雅方式,为具有自引用嵌套结构的类(如树形 message 对象)实现深度优先的递归遍历,无需外部依赖,纯 jdk 实现,支持链式调用与函数式组合。
在构建消息系统、组织架构树或任意具有父子关系的领域模型时,常会遇到类似 Message 这样的自引用类:它既包含自身属性(如 id、status),又持有一个同类型对象的列表(List<Message> children)。此时,一个典型需求是——从任一节点出发,一次性获取其自身及其所有后代节点(即整棵子树)。
最直观的思路是手写递归方法,但易出错、难以复用,且与现代 Java 的函数式风格脱节。更优解是借助 Stream 构建惰性、可组合的递归流:
class Message {
private int id;
private String status;
private List<Message> children;
// 构造器、getter、setter 略
/**
* 返回以当前节点为根的整棵子树(含自身)的 Stream
* 使用深度优先顺序(DFS),支持空 children 安全处理
*/
public Stream<Message> subtree() {
return Stream.concat(
Stream.of(this), // 当前节点自身
Optional.ofNullable(children)
.orElse(Collections.emptyList())
.stream()
.flatMap(Message::subtree) // 递归展开每个子树
);
}
}✅ 关键设计说明:
- subtree() 方法返回 Stream<Message>,天然支持后续操作(如 filter、map、collect);
- 使用 Optional.ofNullable(children).orElse(...) 替代显式 null 判断,避免 NullPointerException;
- flatMap(Message::subtree) 实现递归展开:每个子节点调用自己的 subtree(),再将所有子流合并为一个扁平流;
- Stream.concat(Stream.of(this), ...) 确保当前节点始终位于结果流首位,符合“自身 + 所有后代”的语义。
? 使用示例:
立即学习“Java免费学习笔记(深入)”;
Message root = new Message(1, "ROOT"); Message child1 = new Message(2, "CHILD1"); Message grandChild = new Message(3, "GRANDCHILD"); child1.setChildren(Arrays.asList(grandChild)); root.setChildren(Arrays.asList(child1)); // 获取整棵树(含 root 自身) List<Message> allNodes = root.subtree().collect(Collectors.toList()); System.out.println(allNodes.size()); // 输出: 3 // 顺序: [root, child1, grandChild]
⚠️ 注意事项:
- 此方案默认按深度优先(DFS) 遍历。若需广度优先(BFS),应改用队列迭代实现,Stream 递归天然不适用;
- 无限循环风险:若对象图中存在环(如 A → B → A),此递归将栈溢出。生产环境建议增加访问标记(如 Set<Object> visited)做环检测;
- Stream 是惰性求值的,subtree() 调用本身不触发计算,仅在终端操作(如 collect、forEach)时执行,利于性能优化与条件过滤。
? 进阶提示:
可进一步封装为通用工具方法,结合泛型与函数式接口,适配任意树形结构:
public static <T> Stream<T> traverseTree(T root, Function<T, List<T>> childrenExtractor) {
return Stream.concat(
Stream.of(root),
Optional.ofNullable(childrenExtractor.apply(root))
.orElse(Collections.emptyList())
.stream()
.flatMap(child -> traverseTree(child, childrenExtractor))
);
}
// 调用:traverseTree(message, m -> m.getChildren()).collect(toList());综上,通过 Stream.concat 与 flatMap 的组合,我们以声明式、安全、可扩展的方式解决了 Java 中树形结构的递归遍历问题——代码简洁、语义清晰、符合 JDK 最佳实践。










