这是一套可以解决各种类型的数据构建树形结构的解决方案
方案一(推荐)
ITree - 树数据接口
import org.apache.commons.lang.StringUtils;
import java.util.List;
/**
* 树接口
*
* @author: Neo
* @date: 2021/1/4 14:24
* @version: 1.0
*/
public interface ITree<T, K> {
/**
* 节点ID
*
* @return
*/
K id();
/**
* 父节点ID
*
* @return
*/
K parentId();
/**
* 设置父节点
*
* @param parent
*/
default void parent(T parent){
}
/**
* 用于拼接 path 的属性
*
* child.path(parentNode.path() + spliterator + child.pathProperty());
*
* @return
*/
default String pathProperty(){
return StringUtils.EMPTY;
}
/**
* 获取节点名称
*
* @return
*/
default String path() {
return StringUtils.EMPTY;
}
/**
* 设置节点名称
*
* @param path
*/
default void path(String path) {
}
/**
* 排序
*
* @return
*/
default Integer index() {
return 0;
}
/**
* 设置子节点
*
* @param children
*/
default void children(List<T> children) {
}
}
TreeUtils - 万物皆可 Tree 工具类
import org.apache.commons.collections.CollectionUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* 将扁平数据构造成树结构
*
* @author: Neo
* @date: 2021/1/4 16:49
* @version: 1.0
*/
public class TreeUtils {
public static final String DEFAULT_SPLITERATOR = "/";
private TreeUtils() {
}
enum Model {
TREE, PATH, TREE_AND_PATH
}
/**
* 构造树的入口方法
*
* @param originData 数据列表
* @param rootPredicate 根节点条件
* @param <T> implements ITree
* @param <K> implements Serializable
* @return
*/
public static <T extends ITree<T, K>, K extends Serializable> List<T> buildTree(List<T> originData, Predicate<T> rootPredicate) {
return baseBuild(Model.TREE, originData, null, rootPredicate);
}
/**
* 构建树路径,不改变数据结构
*
* @param originData
* @param rootPredicate
* @param <T>
* @param <K>
* @return
*/
public static <T extends ITree<T, K>, K extends Serializable> List<T> buildPath(List<T> originData, Predicate<T> rootPredicate) {
return buildPath(originData, DEFAULT_SPLITERATOR, rootPredicate);
}
/**
* 构建树路径,不改变数据结构
*
* @param originData
* @param spliterator
* @param rootPredicate
* @param <T>
* @param <K>
* @return
*/
public static <T extends ITree<T, K>, K extends Serializable> List<T> buildPath(List<T> originData, CharSequence spliterator, Predicate<T> rootPredicate) {
return baseBuild(Model.PATH, originData, spliterator, rootPredicate);
}
/**
* 构建树和路径
*
* @param originData
* @param rootPredicate
* @param <T>
* @param <K>
* @return
*/
public static <T extends ITree<T, K>, K extends Serializable> List<T> buildTreeAndPath(List<T> originData, Predicate<T> rootPredicate) {
return buildTreeAndPath(originData, DEFAULT_SPLITERATOR, rootPredicate);
}
/**
* 构建树和路径
*
* @param originData
* @param spliterator
* @param rootPredicate
* @param <T>
* @param <K>
* @return
*/
public static <T extends ITree<T, K>, K extends Serializable> List<T> buildTreeAndPath(List<T> originData, CharSequence spliterator, Predicate<T> rootPredicate) {
return baseBuild(Model.TREE_AND_PATH, originData, spliterator, rootPredicate);
}
/**
* 树构建的基础方法
*
* @param model
* @param originData
* @param spliterator
* @param rootPredicate
* @param <T>
* @param <K>
* @return
*/
public static <T extends ITree<T, K>, K extends Serializable> List<T> baseBuild(Model model,
List<T> originData,
CharSequence spliterator,
Predicate<T> rootPredicate) {
if (Objects.isNull(model) || CollectionUtils.isEmpty(originData) || Objects.isNull(rootPredicate)) {
return Collections.EMPTY_LIST;
}
List<T> result = new ArrayList<>(CollectionUtils.size(originData));
List<T> roots = originData.stream().filter(rootPredicate).sorted(Comparator.comparing(T::index)).collect(Collectors.toList());
if (CollectionUtils.isEmpty(roots)) {
return Collections.EMPTY_LIST;
}
// 删除根节点,避免重复遍历
originData.removeAll(roots);
switch (model) {
case TREE:
roots.forEach(r -> result.add(buildTree(r, originData)));
break;
case PATH:
roots.forEach(r -> result.addAll(buildPath(r, originData, spliterator)));
break;
case TREE_AND_PATH:
roots.forEach(r -> result.add(buildTreeAndPath(r, originData, spliterator)));
break;
default:
throw new RuntimeException();
}
return result;
}
/**
* 构建子节点
*
* @param parentNode 父节点
* @param originData 数据列表
* @param <T> implements ITree
* @param <K> implements Serializable
* @return
*/
public static <T extends ITree<T, K>, K extends Serializable> T buildTree(T parentNode, List<T> originData) {
List<T> childrenNode = new ArrayList<>();
List<T> children = filterByParentId(parentNode.id(), originData);
if (CollectionUtils.isNotEmpty(children)) {
// 删除节点,避免重复遍历
originData.removeAll(children);
}
for (T child : children) {
child.parent(parentNode);
childrenNode.add(buildTree(child, originData));
}
parentNode.children(childrenNode);
return parentNode;
}
/**
* 构建子节点路径
*
* @param parentNode
* @param originData
* @param spliterator
* @param <T>
* @param <K>
* @return
*/
public static <T extends ITree<T, K>, K extends Serializable> List<T> buildPath(T parentNode, List<T> originData, CharSequence spliterator) {
List<T> result = new ArrayList<>();
List<T> children = filterByParentId(parentNode.id(), originData);
result.add(parentNode);
if (CollectionUtils.isEmpty(children)) {
return result;
}
// 删除节点,避免重复遍历
originData.removeAll(children);
for (T child : children) {
child.parent(parentNode);
child.path(parentNode.path() + spliterator + child.pathProperty());
result.addAll(buildPath(child, originData, spliterator));
}
parentNode.children(children);
return result;
}
/**
* 构建树和路径
*
* @param parentNode
* @param originData
* @param spliterator
* @param <T>
* @param <K>
* @return
*/
public static <T extends ITree<T, K>, K extends Serializable> T buildTreeAndPath(T parentNode, List<T> originData, CharSequence spliterator) {
List<T> children = filterByParentId(parentNode.id(), originData);
if (CollectionUtils.isNotEmpty(children)) {
originData.removeAll(children);
}
for (T child : children) {
child.parent(parentNode);
child.path(parentNode.path() + spliterator + child.path());
buildTreeAndPath(child, originData, spliterator);
}
parentNode.children(children);
return parentNode;
}
/**
* @param parentId 父节点ID
* @param originData 数据列表
* @param <T> implements ITree
* @param <K> implements Serializable
* @return
*/
public static <T extends ITree<T, K>, K extends Serializable> List<T> filterByParentId(K parentId, List<T> originData) {
return originData.stream()
.filter(i -> Objects.equals(parentId, i.parentId()))
.sorted(Comparator.comparing(T::index))
.collect(Collectors.toList());
}
}
TestTree - 测试对象
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TestTree implements ITree<TestTree, Long>, Serializable {
private static final long serialVersionUID = -9153069020164026942L;
private Long id;
private String name;
private Long parentId;
private Integer index;
private List<TestTree> children;
@Override
public Long id() {
return this.id;
}
@Override
public Long parentId() {
return this.parentId;
}
@Override
public String path() {
return this.name;
}
@Override
public void path(String path) {
this.name = path;
}
@Override
public Integer index() {
return this.index;
}
@Override
public void children(List<TestTree> children) {
this.children = children;
}
}
TreeUtilsTest - 测试类
import cn.hutool.json.JSONStrFormater;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Objects;
public class TreeUtilsTest {
public static void main(String[] args) {
List<TestTree> testTrees;
System.out.println("=========================【buildTree】============================");
testTrees = TreeUtils.buildTree(initData(), tree -> Objects.isNull(tree.parentId()));
System.out.println(JSONStrFormater.format(JSON.toJSONString(testTrees)));
System.out.println("=========================【buildPath】============================");
testTrees = TreeUtils.buildPath(initData(), tree -> Objects.isNull(tree.parentId()));
System.out.println(JSONStrFormater.format(JSON.toJSONString(testTrees)));
System.out.println("======================【buildTreeAndPath】=========================");
testTrees = TreeUtils.buildTreeAndPath(initData(), tree -> Objects.isNull(tree.parentId()));
System.out.println(JSONStrFormater.format(JSON.toJSONString(testTrees)));
}
static List<TestTree> initData(){
return Lists.newArrayList(
TestTree.builder().id(1L).parentId(null).name("一级-1").index(1).build(),
TestTree.builder().id(2L).parentId(null).name("一级-2").index(2).build(),
TestTree.builder().id(3L).parentId(null).name("一级-3").index(3).build(),
TestTree.builder().id(4L).parentId(1L).name("二级-1").index(1).build(),
TestTree.builder().id(5L).parentId(1L).name("二级-2").index(2).build(),
TestTree.builder().id(6L).parentId(1L).name("二级-3").index(3).build(),
TestTree.builder().id(7L).parentId(4L).name("三级-1").index(1).build(),
TestTree.builder().id(8L).parentId(4L).name("三级-2").index(2).build(),
TestTree.builder().id(9L).parentId(4L).name("三级-3").index(3).build()
);
}
}
测试结果
=========================【buildTree】============================
[
{
"children":
[
{
"children":
[
{
"children":
[
],
"id":7,
"index":1,
"name":"三级-1",
"parentId":4
},
{
"children":
[
],
"id":8,
"index":2,
"name":"三级-2",
"parentId":4
},
{
"children":
[
],
"id":9,
"index":3,
"name":"三级-3",
"parentId":4
}
],
"id":4,
"index":1,
"name":"二级-1",
"parentId":1
},
{
"children":
[
],
"id":5,
"index":2,
"name":"二级-2",
"parentId":1
},
{
"children":
[
],
"id":6,
"index":3,
"name":"二级-3",
"parentId":1
}
],
"id":1,
"index":1,
"name":"一级-1"
},
{
"children":
[
],
"id":2,
"index":2,
"name":"一级-2"
},
{
"children":
[
],
"id":3,
"index":3,
"name":"一级-3"
}
]
=========================【buildPath】============================
[
{
"id":1,
"index":1,
"name":"一级-1"
},
{
"id":4,
"index":1,
"name":"一级-1/二级-1",
"parentId":1
},
{
"id":7,
"index":1,
"name":"一级-1/二级-1/三级-1",
"parentId":4
},
{
"id":8,
"index":2,
"name":"一级-1/二级-1/三级-2",
"parentId":4
},
{
"id":9,
"index":3,
"name":"一级-1/二级-1/三级-3",
"parentId":4
},
{
"id":5,
"index":2,
"name":"一级-1/二级-2",
"parentId":1
},
{
"id":6,
"index":3,
"name":"一级-1/二级-3",
"parentId":1
},
{
"id":2,
"index":2,
"name":"一级-2"
},
{
"id":3,
"index":3,
"name":"一级-3"
}
]
======================【buildTreeAndPath】=========================
[
{
"children":
[
{
"children":
[
{
"children":
[
],
"id":7,
"index":1,
"name":"一级-1/二级-1/三级-1",
"parentId":4
},
{
"children":
[
],
"id":8,
"index":2,
"name":"一级-1/二级-1/三级-2",
"parentId":4
},
{
"children":
[
],
"id":9,
"index":3,
"name":"一级-1/二级-1/三级-3",
"parentId":4
}
],
"id":4,
"index":1,
"name":"一级-1/二级-1",
"parentId":1
},
{
"children":
[
],
"id":5,
"index":2,
"name":"一级-1/二级-2",
"parentId":1
},
{
"children":
[
],
"id":6,
"index":3,
"name":"一级-1/二级-3",
"parentId":1
}
],
"id":1,
"index":1,
"name":"一级-1"
},
{
"children":
[
],
"id":2,
"index":2,
"name":"一级-2"
},
{
"children":
[
],
"id":3,
"index":3,
"name":"一级-3"
}
]
方案二
BuildTreeUtils
import org.apache.commons.collections.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 构造树
*
* @author: Neo
* @date: 2020/12/24 17:26
* @version: 1.0
*/
public class BuildTreeUtils {
/**
* @param treeList 数据列表
* @author: Neo
* @aate: 2020/12/24 17:27
* @version: 1.0
*/
public static <T extends BaseTree<T>> List<T> buildTree(List<T> treeList) {
List<T> firstNodeList = buildChildren(0L, treeList);
if (CollectionUtils.isEmpty(firstNodeList)) {
return Collections.EMPTY_LIST;
}
List<T> result = new ArrayList<>();
for (T node : firstNodeList) {
T n = buildChildren(node, treeList);
result.add(n);
}
return result;
}
public static <T extends BaseTree<T>> T buildChildren(T node, List<T> list) {
List<T> childrenNode = new ArrayList<>();
List<T> children = buildChildren(node.getId(), list);
for (T child : children) {
childrenNode.add(buildChildren(child, list));
}
node.setChildren(childrenNode);
return node;
}
public static <T extends BaseTree<T>> List<T> buildChildren(Long parentId, List<T> list) {
return list.stream()
.filter(i -> Objects.equals(parentId, i.getParentId()))
.sorted(Comparator.comparing(T::getIndex))
.collect(Collectors.toList());
}
}
BaseTree
import cn.hutool.core.convert.Convert;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
@Data
public class BaseTree<T> implements Serializable {
private static final long serialVersionUID = -4881070396277928428L;
/**
* 主键ID
*/
private Long id;
/**
* 父编号
*/
private Long parentId;
private Integer viewIndex;
private List<T> children;
public Integer getIndex() {
return Objects.nonNull(this.viewIndex) ? this.viewIndex : Convert.toInt(this.id);
}
}