1、应用场景
在系统设计中,有许多地方都需要用到树型结构,最常见的场景就是电商系统中的商品分类,我们需要将数据库中存储的商品分类取出来并转换为树结构,也需要通过类别的id去过滤出该类别及子类别下的商品信息,这种功能要如何实现呢,所以建立一个构建树工具类,封装构建树的通用方法,方便使用。
2、构建树方法
2.1 代码实现
publicstatic<T>List<T>buildTree(List<T>nodes, Function<T, String>key, Function<T, String>parentKey, Function<T, List<T>>getChildren, BiConsumer<T, List<T>>setChildren) { List<T>tree=newArrayList<>(16); Map<String, T>idNodeMap=nodes.stream().collect(Collectors.toMap(key, e->e, (a, b) ->a, LinkedHashMap::new)); idNodeMap.forEach((k, v) -> { StringparentId=parentKey.apply(v); if (ROOT_NODE.equals(parentId)) { tree.add(v); } else { Tt=idNodeMap.get(parentId); if (ObjectUtil.isNotEmpty(t)) { List<T>children=getChildren.apply(t); if (CollectionUtil.isEmpty(children)) { children=newArrayList<>(16); setChildren.accept(t, children); } children.add(v); } else { tree.add(v); } } }); returntree; }
2.2 代码讲解
创建静态方法 buildTree(),这里需要传递五个参数,List<T> nodes:节点列表,Function<T, String> key:获取 key 的函数接口,Function<T, String> parentKey: 获取父 key 的函数接口,Funcation<T, List<T>> getChildren: 获取子节点集合的函数接口,BiConsumer<T, List<T>> setChildren: 设置子节点集合的函数接口。
方法体中首先定义一个 list 集合并设置初始容量为 16,用于存储树型对象,树型对象一般包含三个属性:节点 id、节点名称和子节点集合。接着通过 stream 流的方式将 nodes集合转为 map, key 在这里可以视为类别的 id,T 可以看作是类别实体对象。
接着遍历这个 map 集合,通过传入的 parentKey 函数接口,然后调用 apply(v)就可以获取对应的 parentId,然后对 parentId 与根节点标识进行比较,如果相同就直接加入集合中,此时视为顶级类别。如果不同的话,从 map 中获取 parentId 对应的值,如果获取为空,直接加入集合中作为顶级类别,如果不为空的话,就需要通过传入的 getChildren 函数接口,获取该节点父类别中的子节点集合,如果该集合为空,说明当前节点为父类别中的第一个子节点,则需要创建一个 children 集合,调用 setChildren 函数将 children 集合设置为父类别的子节点集合,然后将当前节点加入集合中,如果该集合不为空,则直接将当前节点加入到该集合中即可。通过上诉过程的调用,就可以将一个对象集合转化为树型结构.
3、构建过滤树,排除符合exclude的节点
3.1 代码实现
publicstatic<T>voidexcludeTree(List<T>tree, Function<T, List<T>>getChildren, Predicate<T>exclude) { tree.removeIf(e-> { List<T>children=getChildren.apply(e); if (CollectionUtil.isNotEmpty(children)) { excludeTree(children, getChildren, exclude); } returnexclude.test(e); }); }
3.2 代码讲解
函数的输入参数包括一个树结构列表tree,一个获取子节点的函数getChildren,和一个判断节点是否需要排除的条件exclude。
函数的作用是遍历树结构,对每个节点进行判断,如果满足排除条件,则将该节点从树中移除。对于每个节点,还会递归调用excludeTree函数,对其子节点进行相同的处理。
具体步骤如下:
1. 使用Lambda表达式定义一个匿名函数e,作为tree.removeIf的参数。该函数的作用是对树结构中的每个节点进行判断和处理。
2. 在函数内部,首先使用getChildren函数获取当前节点的子节点列表children。
3. 如果children不为空,则递归调用excludeTree函数,对children进行相同的处理。
4. 最后,使用exclude函数对当前节点进行判断。如果满足排除条件,则返回true,表示需要将该节点从树中移除;否则返回false,表示保留该节点。
4、根据树以及节点id获取该节点及以下的节点id
4.1 代码实现
publicstatic<T>List<String>getTreeIds(List<T>tree, StringnodeId, Function<T, String>key, Function<T, List<T>>getChildren) { if (ObjectUtils.isEmpty(tree) ||StringUtils.isBlank(nodeId)) { returnnewArrayList<>(); } List<String>nodeIds=newArrayList<>(); returngetTreeIds(tree, nodeId, nodeIds, key, getChildren); } publicstatic<T>List<String>getTreeIds(List<T>tree, StringnodeId, List<String>nodeIds, Function<T, String>key, Function<T, List<T>>getChildren) { if (CollectionUtil.isNotEmpty(tree)) { tree.forEach(o-> { if (StringUtils.equals(key.apply(o), nodeId)) { nodeIds.add(key.apply(o)); getTreeIds(getChildren.apply(o), nodeIds, key, getChildren); } getTreeIds(getChildren.apply(o), nodeId, nodeIds, key, getChildren); }); } returnnodeIds; } publicstatic<T>voidgetTreeIds(List<T>tree, List<String>nodeIds, Function<T, String>key, Function<T, List<T>>getChildren) { if(ObjectUtils.isNotEmpty(tree)){ tree.forEach(o-> { nodeIds.add(key.apply(o)); if (ObjectUtils.isNotEmpty(getChildren.apply(o))) { getTreeIds(getChildren.apply(o), nodeIds, key, getChildren); } }); } }
4.2 代码讲解
这段代码用于获取树形结构数据中指定节点及其子节点的id列表。它通过递归遍历树形结构,找到目标节点并将其id添加到列表中,然后继续遍历目标节点的子节点,重复上述操作,直到遍历完所有节点。最终返回包含目标节点及其子节点id的列表。