diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 14ffe6b6c..d318bd852 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -5,10 +5,7 @@ import cn.hutool.core.text.StrUtil; import cn.hutool.core.util.ArrayUtil; import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.BinaryOperator; -import java.util.function.Function; -import java.util.function.Supplier; +import java.util.function.*; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -371,4 +368,129 @@ public class CollectorUtil { public static Collector, ?, Map> entryToMap() { return toMap(Map.Entry::getKey, Map.Entry::getValue); } + + /** + *

将集合转换为树,默认用 {@code parentId == null} 来判断树的根节点 + * 因为需要在当前传入数据里查找,所以这是一个结束操作
+ * + * @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId} + * @param pIdGetter parentId的getter对应的lambda,可以写作 {@code Student::getParentId} + * @param childrenSetter children的setter对应的lambda,可以写作{ @code Student::setChildren} + * @param isParallel 是否并行去组装,数据量特别大时使用 + * @param 此处是元素类型 + * @param 此处是id、parentId的泛型限制 + * @return list 组装好的树
+ * eg: + *

{@code
+	 * List studentTree = students.stream().collect(toTree(Student::getId, Student::getParentId, Student::setChildren, isParallel));
+	 * }
+ */ + public static , T> Collector> toTree( + final Function idGetter, + final Function pIdGetter, + final BiConsumer> childrenSetter, + final boolean isParallel) { + return toTree(idGetter, pIdGetter, null, childrenSetter, isParallel); + } + + /** + *

将集合转换为树,默认用 {@code parentId == pidValue} 来判断树的根节点,可以为null + * 因为需要在当前传入数据里查找,所以这是一个结束操作
+ * + * @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId} + * @param pIdGetter parentId的getter对应的lambda,可以写作 {@code Student::getParentId} + * @param pidValue pid的值 + * @param childrenSetter children的setter对应的lambda,可以写作{ @code Student::setChildren} + * @param isParallel 是否并行去组装,数据量特别大时使用 + * @param 此处是元素类型 + * @param 此处是id、parentId的泛型限制 + * @return list 组装好的树
+ * eg: + *

{@code
+	 * List studentTree = students.stream().collect(toTree(Student::getId, Student::getParentId, 0L, Student::setChildren, isParallel));
+	 * }
+ * @author VampireAchao + */ + public static , T> Collector> toTree( + final Function idGetter, + final Function pIdGetter, + final R pidValue, + final BiConsumer> childrenSetter, + final boolean isParallel) { + return Collectors.collectingAndThen(groupingBy(pIdGetter, Collectors.toList()), + getChildrenFromMapByPidAndSet(idGetter, pIdValuesMap -> pIdValuesMap.get(pidValue), childrenSetter, isParallel)); + } + + /** + * 将集合转换为树,自定义根节点的判断条件 + * 因为需要在当前传入数据里查找,所以这是一个结束操作 + * + * @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId} + * @param pIdGetter parentId的getter对应的lambda,可以写作 {@code Student::getParentId} + * @param childrenSetter children的setter对应的lambda,可以写作 {@code Student::setChildren} + * @param parentPredicate 树顶部的判断条件,可以写作 {@code s -> Objects.equals(s.getParentId(),0L) } + * @param isParallel 是否并行处理 + * @param 此处是元素类型 + * @param 此处是id、parentId的泛型限制 + * @return list 组装好的树
+ * eg: + *
{@code
+	 * List studentTree = EasyStream.of(students).
+	 * 	.toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
+	 * }
+ * @author VampireAchao + */ + public static , T> Collector> toTree( + final Function idGetter, + final Function pIdGetter, + final BiConsumer> childrenSetter, + final Predicate parentPredicate, + final boolean isParallel) { + final List parents = new ArrayList<>(); + return Collectors.collectingAndThen(groupingBy(pIdGetter, + new SimpleCollector<>(ArrayList::new, + (acc, e) -> { + if (parentPredicate.test(e)) { + parents.add(e); + } + acc.add(e); + }, + (left, right) -> { + left.addAll(right); + return left; + }, + CH_ID)), + getChildrenFromMapByPidAndSet(idGetter, pIdValuesMap -> parents, childrenSetter, isParallel)); + } + + /** + * toTree的内联函数 + * 因为需要在当前传入数据里查找,所以这是一个结束操作 + * + * @param idGetter id的getter对应的lambda,可以写作 {@code Student::getId} + * @param parentFactory 顶部数据工厂方法 + * @param childrenSetter children的setter对应的lambda,可以写作 {@code Student::setChildren} + * @param isParallel 是否并行处理 + * @param 此处是元素类型 + * @param 此处是id的泛型限制 + * @return list 组装好的树 + * @author VampireAchao + */ + private static , T> Function>, List> getChildrenFromMapByPidAndSet( + final Function idGetter, + final Function>, List> parentFactory, + final BiConsumer> childrenSetter, + final boolean isParallel) { + return pIdValuesMap -> { + EasyStream.of(pIdValuesMap.values(), isParallel).flat(Function.identity()) + .forEach(value -> { + final List children = pIdValuesMap.get(idGetter.apply(value)); + if (children != null) { + childrenSetter.accept(value, children); + } + }); + return parentFactory.apply(pIdValuesMap); + }; + } + } diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/EasyStream.java b/hutool-core/src/main/java/cn/hutool/core/stream/EasyStream.java index f2af24e8b..1fea0642d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/EasyStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/EasyStream.java @@ -4,9 +4,7 @@ import cn.hutool.core.lang.Opt; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjUtil; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Spliterator; import java.util.function.*; @@ -280,14 +278,13 @@ public class EasyStream extends AbstractEnhancedWrappedStream studentTree = EasyStream.of(students). * toTree(Student::getId, Student::getParentId, Student::setChildren); * } + * @author VampireAchao */ public > List toTree( - final Function idGetter, - final Function pIdGetter, - final BiConsumer> childrenSetter) { - // 使用 parentId == null 判断是否为根节点 - final Predicate parentPredicate = node -> null == pIdGetter.apply(node); - return toTree(idGetter, pIdGetter, childrenSetter, parentPredicate); + final Function idGetter, + final Function pIdGetter, + final BiConsumer> childrenSetter) { + return collect(CollectorUtil.toTree(idGetter, pIdGetter, childrenSetter, isParallel())); } /** @@ -305,39 +302,14 @@ public class EasyStream extends AbstractEnhancedWrappedStream studentTree = EasyStream.of(students). * .toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent); * } + * @author VampireAchao */ public > List toTree( - final Function idGetter, - final Function pIdGetter, - final BiConsumer> childrenSetter, - final Predicate parentPredicate) { - Objects.requireNonNull(idGetter); - Objects.requireNonNull(pIdGetter); - Objects.requireNonNull(childrenSetter); - Objects.requireNonNull(parentPredicate); - - List nodeList = toList(); - // 根据 父id 分组,让key为null的组中全是根节点 - final Function pIdClassifier = node -> { - // 该节点是根节点, 分到 父id 为null的组中 - if (parentPredicate.test(node)) { - return null; - } - // 返回 父id - return pIdGetter.apply(node); - }; - // 父id 关联的 子节点列表 - final Map> pId2ChildrenMap = of(nodeList).group(pIdClassifier); - - of(nodeList, true).forEach(node -> { - // 设置 该节点的子节点列表 - final List children = pId2ChildrenMap.get(idGetter.apply(node)); - if (children != null) { - childrenSetter.accept(node, children); - } - }); - // 返回根节点列表 - return pId2ChildrenMap.getOrDefault(null, Collections.emptyList()); + final Function idGetter, + final Function pIdGetter, + final BiConsumer> childrenSetter, + final Predicate parentPredicate) { + return collect(CollectorUtil.toTree(idGetter, pIdGetter, childrenSetter, parentPredicate, isParallel())); } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/stream/EasyStreamTest.java b/hutool-core/src/test/java/cn/hutool/core/stream/EasyStreamTest.java index 9ea222491..166a3f201 100644 --- a/hutool-core/src/test/java/cn/hutool/core/stream/EasyStreamTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/stream/EasyStreamTest.java @@ -182,7 +182,7 @@ public class EasyStreamTest { Assert.assertEquals(collect2, distinctBy2); Assert.assertEquals( - 4, EasyStream.of(1, 2, 2, null, 3, null).parallel(true).distinct(t -> Objects.isNull(t) ? null : t.toString()).sequential().count() + 4, EasyStream.of(1, 2, 2, null, 3, null).parallel(true).distinct(t -> Objects.isNull(t) ? null : t.toString()).sequential().count() ); } @@ -477,7 +477,7 @@ public class EasyStreamTest { Student.builder().id(8L).name("jobob").parentId(5L).build() ) // just 4 lambda ,top by condition - .toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent); + .toTree(Student::getId, Student::getParentId, Student::setChildren, s -> BooleanUtil.isTrue(s.getMatchParent())); Assert.assertEquals(asList( Student.builder().id(1L).name("dromara").matchParent(true) .children(asList(Student.builder().id(3L).name("hutool").parentId(1L) @@ -547,10 +547,6 @@ public class EasyStreamTest { public Student() { // this is an accessible parameterless constructor. } - - public Boolean getMatchParent() { - return BooleanUtil.isTrue(matchParent); - } } @Test