From 9e20dbb7a043982e6641d91987c63b098ca1c945 Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 30 Aug 2022 15:00:59 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E5=B0=86EasyStream=E9=83=A8=E5=88=86?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E6=96=B9=E6=B3=95=E5=88=86=E7=A6=BB=E8=87=B3?= =?UTF-8?q?=E5=85=AC=E5=85=B1=E7=88=B6=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/stream/EasyStream.java | 570 +----------------- .../cn/hutool/core/stream/StreamWrapper.java | 563 +++++++++++++++++ 2 files changed, 574 insertions(+), 559 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/stream/StreamWrapper.java 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 05ab42fa1..5de688e5c 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 @@ -9,41 +9,15 @@ import cn.hutool.core.lang.mutable.MutableObj; import cn.hutool.core.map.MapUtil; import cn.hutool.core.text.StrUtil; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjUtil; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.Spliterator; -import java.util.Spliterators; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.BiPredicate; -import java.util.function.BinaryOperator; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.function.ToDoubleFunction; -import java.util.function.ToIntFunction; -import java.util.function.ToLongFunction; -import java.util.function.UnaryOperator; +import java.util.function.*; import java.util.stream.Collector; import java.util.stream.Collectors; -import java.util.stream.DoubleStream; -import java.util.stream.IntStream; -import java.util.stream.LongStream; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -82,21 +56,19 @@ import java.util.stream.StreamSupport; * @see java.util.stream.Stream * @since 6.0.0 */ -public class EasyStream implements Stream, Iterable { +public class EasyStream extends StreamWrapper> implements Stream, Iterable { /** * 代表不存在的下标, 一般用于并行流的下标, 或者未找到元素时的下标 */ private static final int NOT_FOUND_INDEX = -1; - protected final Stream stream; - /** * 构造 * * @param stream {@link Stream} */ EasyStream(final Stream stream) { - this.stream = null == stream ? Stream.empty() : stream; + super(ObjUtil.isNull(stream) ? Stream.empty() : stream); } // region Static method @@ -280,18 +252,6 @@ public class EasyStream implements Stream, Iterable { // --------------------------------------------------------------- Static method end // endregion - /** - * 过滤元素,返回与指定断言匹配的元素组成的流 - * 这是一个无状态中间操作 - * - * @param predicate 断言 - * @return 返回叠加过滤操作后的流 - */ - @Override - public EasyStream filter(final Predicate predicate) { - return new EasyStream<>(stream.filter(predicate)); - } - /** * 过滤元素,返回与 指定操作结果 匹配 指定值 的元素组成的流 * 这是一个无状态中间操作 @@ -414,42 +374,6 @@ public class EasyStream implements Stream, Iterable { } } - /** - * 和{@link EasyStream#map(Function)}一样,只不过函数的返回值必须为int类型 - * 这是一个无状态中间操作 - * - * @param mapper 返回值为int类型的函数 - * @return 叠加操作后元素类型全为int的流 - */ - @Override - public IntStream mapToInt(final ToIntFunction mapper) { - return stream.mapToInt(mapper); - } - - /** - * 和{@link EasyStream#map(Function)}一样,只不过函数的返回值必须为long类型 - * 这是一个无状态中间操作 - * - * @param mapper 返回值为long类型的函数 - * @return 叠加操作后元素类型全为long的流 - */ - @Override - public LongStream mapToLong(final ToLongFunction mapper) { - return stream.mapToLong(mapper); - } - - /** - * 和{@link EasyStream#map(Function)}一样,只不过函数的返回值必须为double类型 - * 这是一个无状态中间操作 - * - * @param mapper 返回值为double类型的函数 - * @return 叠加操作后元素类型全为double的流 - */ - @Override - public DoubleStream mapToDouble(final ToDoubleFunction mapper) { - return stream.mapToDouble(mapper); - } - /** * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作, 转换为迭代器元素, * 最后返回所有迭代器的所有元素组成的流
@@ -483,42 +407,6 @@ public class EasyStream implements Stream, Iterable { return nonNull().flat(mapper).nonNull(); } - /** - * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作,返回多个流所有元素组成的流 - * 这是一个无状态中间操作 - * - * @param mapper 操作,返回IntStream - * @return 返回叠加拆分操作后的IntStream - */ - @Override - public IntStream flatMapToInt(final Function mapper) { - return stream.flatMapToInt(mapper); - } - - /** - * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作,返回多个流所有元素组成的流 - * 这是一个无状态中间操作 - * - * @param mapper 操作,返回LongStream - * @return 返回叠加拆分操作后的LongStream - */ - @Override - public LongStream flatMapToLong(final Function mapper) { - return stream.flatMapToLong(mapper); - } - - /** - * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作,返回多个流所有元素组成的流 - * 这是一个无状态中间操作 - * - * @param mapper 操作,返回DoubleStream - * @return 返回叠加拆分操作后的DoubleStream - */ - @Override - public DoubleStream flatMapToDouble(final Function mapper) { - return stream.flatMapToDouble(mapper); - } - /** * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作,返回多个流所有元素组成的流,操作带一个方法,调用该方法可增加元素 * 这是一个无状态中间操作 @@ -536,17 +424,6 @@ public class EasyStream implements Stream, Iterable { }); } - /** - * 返回一个具有去重特征的流 非并行流(顺序流)下对于重复元素,保留遇到顺序中最先出现的元素,并行流情况下不能保证具体保留哪一个 - * 这是一个有状态中间操作 - * - * @return 一个具有去重特征的流 - */ - @Override - public EasyStream distinct() { - return new EasyStream<>(stream.distinct()); - } - /** * 返回一个具有去重特征的流 非并行流(顺序流)下对于重复元素,保留遇到顺序中最先出现的元素,并行流情况下不能保证具体保留哪一个 * 这是一个有状态中间操作 @@ -582,55 +459,6 @@ public class EasyStream implements Stream, Iterable { } } - /** - * 返回一个元素按自然顺序排序的流 - * 如果此流的元素不是{@code Comparable} ,则在执行终端操作时可能会抛出 {@code java.lang.ClassCastException} - * 对于顺序流,排序是稳定的。 对于无序流,没有稳定性保证。 - * 这是一个有状态中间操作 - * - * @return 一个元素按自然顺序排序的流 - */ - @Override - public EasyStream sorted() { - return new EasyStream<>(stream.sorted()); - } - - /** - * 返回一个元素按指定的{@link Comparator}排序的流 - * 如果此流的元素不是{@code Comparable} ,则在执行终端操作时可能会抛出{@code java.lang.ClassCastException} - * 对于顺序流,排序是稳定的。 对于无序流,没有稳定性保证。 - * 这是一个有状态中间操作 - * - * @param comparator 排序规则 - * @return 一个元素按指定的Comparator排序的流 - */ - @Override - public EasyStream sorted(final Comparator comparator) { - return new EasyStream<>(stream.sorted(comparator)); - } - - /** - * 返回与指定函数将元素作为参数执行后组成的流。 - * 这是一个无状态中间操作 - * - * @param action 指定的函数 - * @return 返回叠加操作后的FastStream - * @apiNote 该方法存在的意义主要是用来调试 - * 当你需要查看经过操作管道某处的元素,可以执行以下操作: - *
{@code
-	 *     .of("one", "two", "three", "four")
-	 *         .filter(e -> e.length() > 3)
-	 *         .peek(e -> System.out.println("Filtered value: " + e))
-	 *         .map(String::toUpperCase)
-	 *         .peek(e -> System.out.println("Mapped value: " + e))
-	 *         .collect(Collectors.toList());
-	 * }
- */ - @Override - public EasyStream peek(final Consumer action) { - return new EasyStream<>(stream.peek(action)); - } - /** * 返回与指定函数将元素作为参数执行后组成的流。操作带下标,并行流时下标永远为-1 * 这是一个无状态中间操作 @@ -656,6 +484,7 @@ public class EasyStream implements Stream, Iterable { return peek(e -> action.accept(e, index.incrementAndGet())); } } + /** * 返回叠加调用{@link Console#log(Object)}打印出结果的流 * @@ -665,53 +494,6 @@ public class EasyStream implements Stream, Iterable { return peek(Console::log); } - /** - * 返回截取后面一些元素的流 - * 这是一个短路状态中间操作 - * - * @param maxSize 元素截取后的个数 - * @return 截取后的流 - */ - @Override - public EasyStream limit(final long maxSize) { - return new EasyStream<>(stream.limit(maxSize)); - } - - /** - * 返回丢弃前面n个元素后的剩余元素组成的流,如果当前元素个数小于n,则返回一个元素为空的流 - * 这是一个有状态中间操作 - * - * @param n 需要丢弃的元素个数 - * @return 丢弃前面n个元素后的剩余元素组成的流 - */ - @Override - public EasyStream skip(final long n) { - return new EasyStream<>(stream.skip(n)); - } - - /** - * 返回一个串行流,该方法可以将并行流转换为串行流 - * - * @return 串行流 - */ - @Override - public EasyStream sequential() { - //noinspection ResultOfMethodCallIgnored - stream.sequential(); - return this; - } - - /** - * 对流里面的每一个元素执行一个操作 - * 这是一个终端操作 - * - * @param action 操作 - */ - @Override - public void forEach(final Consumer action) { - stream.forEach(action); - } - /** * 对流里面的每一个元素执行一个操作,操作带下标,并行流时下标永远为-1 * 这是一个终端操作 @@ -728,17 +510,6 @@ public class EasyStream implements Stream, Iterable { } } - /** - * 对流里面的每一个元素按照顺序执行一个操作 - * 这是一个终端操作 - * - * @param action 操作 - */ - @Override - public void forEachOrdered(final Consumer action) { - stream.forEachOrdered(action); - } - /** * 对流里面的每一个元素按照顺序执行一个操作,操作带下标,并行流时下标永远为-1 * 这是一个终端操作 @@ -755,216 +526,6 @@ public class EasyStream implements Stream, Iterable { } } - /** - * 返回一个包含此流元素的数组 - * 这是一个终端操作 - * - * @return 包含此流元素的数组 - */ - @Override - public Object[] toArray() { - return stream.toArray(); - } - - /** - * 返回一个包含此流元素的指定的数组,例如以下代码编译正常,但运行时会抛出 {@link ArrayStoreException} - *
{@code String[] strings = Stream.builder().add(1).build().toArray(String[]::new); }
- * - * @param generator 这里的IntFunction的参数是元素的个数,返回值为数组类型 - * @param 给定的数组类型 - * @return 包含此流元素的指定的数组 - * @throws ArrayStoreException 如果元素转换失败,例如不是该元素类型及其父类,则抛出该异常 - */ - @Override - public A[] toArray(final IntFunction generator) { - //noinspection SuspiciousToArrayCall - return stream.toArray(generator); - } - - /** - * 对元素进行聚合,并返回聚合后的值,相当于在for循环里写sum=sum+ints[i] - * 这是一个终端操作
- * 求和、最小值、最大值、平均值和转换成一个String字符串均为聚合操作 - * 例如这里对int进行求和可以写成: - * - *
{@code
-	 *     Integer sum = integers.reduce(0, (a, b) -> a+b);
-	 * }
- *

- * 或者写成: - * - *

{@code
-	 *     Integer sum = integers.reduce(0, Integer::sum);
-	 * }
- * - * @param identity 初始值,还用于限定泛型 - * @param accumulator 你想要的聚合操作 - * @return 聚合计算后的值 - */ - @Override - public T reduce(final T identity, final BinaryOperator accumulator) { - return stream.reduce(identity, accumulator); - } - - /** - * 对元素进行聚合,并返回聚合后用 {@link Optional}包裹的值,相当于在for循环里写sum=sum+ints[i] - * 该操作相当于: - *
{@code
-	 *     boolean foundAny = false;
-	 *     T result = null;
-	 *     for (T element : this stream) {
-	 *         if (!foundAny) {
-	 *             foundAny = true;
-	 *             result = element;
-	 *         }
-	 *         else
-	 *             result = accumulator.apply(result, element);
-	 *     }
-	 *     return foundAny ? Optional.of(result) : Optional.empty();
-	 * }
- * 但它不局限于顺序执行,例如并行流等情况下 - * 这是一个终端操作
- * 例如以下场景抛出 NPE : - *
{@code
-	 *      Optional reduce = Stream.builder().add(1).add(1).build().reduce((a, b) -> null);
-	 * }
- * - * @param accumulator 你想要的聚合操作 - * @return 聚合后用 {@link Optional}包裹的值 - * @throws NullPointerException 如果给定的聚合操作中执行后结果为空,并用于下一次执行,则抛出该异常 - * @see #reduce(Object, BinaryOperator) - * @see #min(Comparator) - * @see #max(Comparator) - */ - @Override - public Optional reduce(final BinaryOperator accumulator) { - return stream.reduce(accumulator); - } - - /** - * 对元素进行聚合,并返回聚合后的值,并行流时聚合拿到的初始值不稳定 - * 这是一个终端操作 - * - * @param identity 初始值 - * @param accumulator 累加器,具体为你要的聚合操作 - * @param combiner 用于并行流时组合多个结果 - * @param 初始值 - * @return 聚合操作的结果 - * @see #reduce(BinaryOperator) - * @see #reduce(Object, BinaryOperator) - */ - @Override - public U reduce(final U identity, final BiFunction accumulator, final BinaryOperator combiner) { - return stream.reduce(identity, accumulator, combiner); - } - - /** - * 对元素进行收集,并返回收集后的容器 - * 这是一个终端操作 - * - * @param supplier 提供初始值的函数式接口,一般可以传入构造参数 - * @param accumulator 具体收集操作 - * @param combiner 用于并行流时组合多个结果 - * @param 用于收集元素的容器,大多是集合 - * @return 收集后的容器 - *
{@code
-	 *  List collect = Stream.iterate(1, i -> ++i).limit(10).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
-	 * }
- */ - @Override - public R collect(final Supplier supplier, final BiConsumer accumulator, final BiConsumer combiner) { - return stream.collect(supplier, accumulator, combiner); - } - - /** - * 对元素进行收集,并返回收集后的元素 - * 这是一个终端操作 - * - * @param collector 收集器 - * @param 容器类型 - * @param
具体操作时容器类型,例如 {@code List::add} 时它为 {@code List} - * @return 收集后的容器 - */ - @Override - public R collect(final Collector collector) { - return stream.collect(collector); - } - - /** - * 获取最小值 - * - * @param comparator 一个用来比较大小的比较器{@link Comparator} - * @return 最小值 - */ - @Override - public Optional min(final Comparator comparator) { - return stream.min(comparator); - } - - /** - * 获取最大值 - * - * @param comparator 一个用来比较大小的比较器{@link Comparator} - * @return 最大值 - */ - @Override - public Optional max(final Comparator comparator) { - return stream.max(comparator); - } - - /** - * 返回流元素个数 - * - * @return 流元素个数 - */ - @Override - public long count() { - return stream.count(); - } - - /** - * 判断是否有任何一个元素满足给定断言 - * - * @param predicate 断言 - * @return 是否有任何一个元素满足给定断言 - */ - @Override - public boolean anyMatch(final Predicate predicate) { - return stream.anyMatch(predicate); - } - - /** - * 判断是否所有元素满足给定断言 - * - * @param predicate 断言 - * @return 是否所有元素满足给定断言 - */ - @Override - public boolean allMatch(final Predicate predicate) { - return stream.allMatch(predicate); - } - - /** - * 判断是否没有元素满足给定断言 - * - * @param predicate 断言 - * @return 是否没有元素满足给定断言 - */ - @Override - public boolean noneMatch(final Predicate predicate) { - return stream.noneMatch(predicate); - } - - /** - * 获取第一个元素 - * - * @return 第一个元素 - */ - @Override - public Optional findFirst() { - return stream.findFirst(); - } - /** * 获取与给定断言匹配的第一个元素 * @@ -1057,48 +618,6 @@ public class EasyStream implements Stream, Iterable { return of(array).parallel(isParallel()).onClose(stream::close); } - /** - * 考虑性能,随便取一个,这里不是随机取一个,是随便取一个 - * - * @return 随便取一个 - */ - @Override - public Optional findAny() { - return stream.findAny(); - } - - /** - * 返回流的迭代器 - * - * @return 流的迭代器 - */ - @Override - public Iterator iterator() { - return stream.iterator(); - } - - /** - * 返回流的拆分器 - * - * @return 流的拆分器 - */ - @Override - public Spliterator spliterator() { - return stream.spliterator(); - } - - /** - * 将流转换为并行 - * - * @return 并行流 - */ - @Override - public EasyStream parallel() { - //noinspection ResultOfMethodCallIgnored - stream.parallel(); - return this; - } - /** * 更改流的并行状态 * @@ -1109,30 +628,6 @@ public class EasyStream implements Stream, Iterable { return parallel ? parallel() : sequential(); } - /** - * 返回一个无序流(无手动排序) - *

标记一个流是不在意元素顺序的, 在并行流的某些情况下可以提高性能

- * - * @return 无序流 - */ - @Override - public EasyStream unordered() { - return new EasyStream<>(stream.unordered()); - } - - /** - * 在流关闭时执行操作 - * - * @param closeHandler 在流关闭时执行的操作 - * @return 流 - */ - @Override - public EasyStream onClose(final Runnable closeHandler) { - //noinspection ResultOfMethodCallIgnored - stream.onClose(closeHandler); - return this; - } - /** * 与给定元素组成的流合并,成为新的流 * @@ -1187,57 +682,14 @@ public class EasyStream implements Stream, Iterable { } /** - * 返回流的并行状态 + * 根据一个原始的流,返回一个新包装类实例 * - * @return 流的并行状态 + * @param stream 流 + * @return 实现类 */ @Override - public boolean isParallel() { - return stream.isParallel(); - } - - /** - * 关闭流 - * - * @see AutoCloseable#close() - */ - @Override - public void close() { - stream.close(); - } - - /** - * hashcode - * - * @return hashcode - */ - @Override - public int hashCode() { - return stream.hashCode(); - } - - /** - * equals - * - * @param obj 对象 - * @return 结果 - */ - @Override - public boolean equals(final Object obj) { - if (obj instanceof Stream) { - return stream.equals(obj); - } - return false; - } - - /** - * toString - * - * @return string - */ - @Override - public String toString() { - return stream.toString(); + protected EasyStream convertToStreamImpl(Stream stream) { + return new EasyStream<>(stream); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/StreamWrapper.java b/hutool-core/src/main/java/cn/hutool/core/stream/StreamWrapper.java new file mode 100644 index 000000000..60da8e88d --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/stream/StreamWrapper.java @@ -0,0 +1,563 @@ +package cn.hutool.core.stream; + +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +/** + * {@link Stream}的包装类,用于基于一个已有的流实例进行扩展 + * + * @author huangchengxing + * @see EasyStream + */ +abstract class StreamWrapper> implements Stream, Iterable { + + /** + * 原始的流实例 + */ + protected final Stream stream; + + /** + * 创建一个流包装器 + * + * @param stream 包装的流对象 + */ + protected StreamWrapper(Stream stream) { + Objects.requireNonNull(stream, "stream must not null"); + this.stream = stream; + } + + /** + * 过滤元素,返回与指定断言匹配的元素组成的流 + * 这是一个无状态中间操作 + * + * @param predicate 断言 + * @return 返回叠加过滤操作后的流 + */ + @Override + public I filter(Predicate predicate) { + return convertToStreamImpl(stream.filter(predicate)); + } + + /** + * 和{@link EasyStream#map(Function)}一样,只不过函数的返回值必须为int类型 + * 这是一个无状态中间操作 + * + * @param mapper 返回值为int类型的函数 + * @return 叠加操作后元素类型全为int的流 + */ + @Override + public IntStream mapToInt(ToIntFunction mapper) { + return stream.mapToInt(mapper); + } + + /** + * 和{@link EasyStream#map(Function)}一样,只不过函数的返回值必须为long类型 + * 这是一个无状态中间操作 + * + * @param mapper 返回值为long类型的函数 + * @return 叠加操作后元素类型全为long的流 + */ + @Override + public LongStream mapToLong(ToLongFunction mapper) { + return stream.mapToLong(mapper); + } + + /** + * 和{@link EasyStream#map(Function)}一样,只不过函数的返回值必须为double类型 + * 这是一个无状态中间操作 + * + * @param mapper 返回值为double类型的函数 + * @return 叠加操作后元素类型全为double的流 + */ + @Override + public DoubleStream mapToDouble(ToDoubleFunction mapper) { + return stream.mapToDouble(mapper); + } + + /** + * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作,返回多个流所有元素组成的流 + * 这是一个无状态中间操作 + * + * @param mapper 操作,返回IntStream + * @return 返回叠加拆分操作后的IntStream + */ + @Override + public IntStream flatMapToInt(Function mapper) { + return stream.flatMapToInt(mapper); + } + + /** + * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作,返回多个流所有元素组成的流 + * 这是一个无状态中间操作 + * + * @param mapper 操作,返回LongStream + * @return 返回叠加拆分操作后的LongStream + */ + @Override + public LongStream flatMapToLong(Function mapper) { + return stream.flatMapToLong(mapper); + } + + /** + * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作,返回多个流所有元素组成的流 + * 这是一个无状态中间操作 + * + * @param mapper 操作,返回DoubleStream + * @return 返回叠加拆分操作后的DoubleStream + */ + @Override + public DoubleStream flatMapToDouble(Function mapper) { + return stream.flatMapToDouble(mapper); + } + + /** + * 返回一个具有去重特征的流 非并行流(顺序流)下对于重复元素,保留遇到顺序中最先出现的元素,并行流情况下不能保证具体保留哪一个 + * 这是一个有状态中间操作 + * + * @return 一个具有去重特征的流 + */ + @Override + public I distinct() { + return convertToStreamImpl(stream.distinct()); + } + + /** + * 返回一个元素按自然顺序排序的流 + * 如果此流的元素不是{@code Comparable} ,则在执行终端操作时可能会抛出 {@code java.lang.ClassCastException} + * 对于顺序流,排序是稳定的。 对于无序流,没有稳定性保证。 + * 这是一个有状态中间操作 + * + * @return 一个元素按自然顺序排序的流 + */ + @Override + public I sorted() { + return convertToStreamImpl(stream.sorted()); + } + + /** + * 返回一个元素按指定的{@link Comparator}排序的流 + * 如果此流的元素不是{@code Comparable} ,则在执行终端操作时可能会抛出{@code java.lang.ClassCastException} + * 对于顺序流,排序是稳定的。 对于无序流,没有稳定性保证。 + * 这是一个有状态中间操作 + * + * @param comparator 排序规则 + * @return 一个元素按指定的Comparator排序的流 + */ + @Override + public I sorted(Comparator comparator) { + return convertToStreamImpl(stream.sorted(comparator)); + } + + /** + * 返回与指定函数将元素作为参数执行后组成的流。 + * 这是一个无状态中间操作 + * + * @param action 指定的函数 + * @return 返回叠加操作后的FastStream + * @apiNote 该方法存在的意义主要是用来调试 + * 当你需要查看经过操作管道某处的元素,可以执行以下操作: + *
{@code
+	 *     .of("one", "two", "three", "four")
+	 *         .filter(e -> e.length() > 3)
+	 *         .peek(e -> System.out.println("Filtered value: " + e))
+	 *         .map(String::toUpperCase)
+	 *         .peek(e -> System.out.println("Mapped value: " + e))
+	 *         .collect(Collectors.toList());
+	 * }
+ */ + @Override + public I peek(Consumer action) { + return convertToStreamImpl(stream.peek(action)); + } + + /** + * 返回截取后面一些元素的流 + * 这是一个短路状态中间操作 + * + * @param maxSize 元素截取后的个数 + * @return 截取后的流 + */ + @Override + public I limit(long maxSize) { + return convertToStreamImpl(stream.limit(maxSize)); + } + + /** + * 返回丢弃前面n个元素后的剩余元素组成的流,如果当前元素个数小于n,则返回一个元素为空的流 + * 这是一个有状态中间操作 + * + * @param n 需要丢弃的元素个数 + * @return 丢弃前面n个元素后的剩余元素组成的流 + */ + @Override + public I skip(long n) { + return convertToStreamImpl(stream.skip(n)); + } + + /** + * 对流里面的每一个元素执行一个操作 + * 这是一个终端操作 + * + * @param action 操作 + */ + @Override + public void forEach(Consumer action) { + stream.forEach(action); + } + + /** + * 对流里面的每一个元素按照顺序执行一个操作 + * 这是一个终端操作 + * + * @param action 操作 + */ + @Override + public void forEachOrdered(Consumer action) { + stream.forEachOrdered(action); + } + + /** + * 返回一个包含此流元素的数组 + * 这是一个终端操作 + * + * @return 包含此流元素的数组 + */ + @Override + public Object[] toArray() { + return stream.toArray(); + } + + /** + * 返回一个包含此流元素的指定的数组,例如以下代码编译正常,但运行时会抛出 {@link ArrayStoreException} + *
{@code String[] strings = Stream.builder().add(1).build().toArray(String[]::new); }
+ * + * @param generator 这里的IntFunction的参数是元素的个数,返回值为数组类型 + * @param
给定的数组类型 + * @return 包含此流元素的指定的数组 + * @throws ArrayStoreException 如果元素转换失败,例如不是该元素类型及其父类,则抛出该异常 + */ + @Override + public A[] toArray(IntFunction generator) { + return stream.toArray(generator); + } + + /** + * 对元素进行聚合,并返回聚合后的值,相当于在for循环里写sum=sum+ints[i] + * 这是一个终端操作
+ * 求和、最小值、最大值、平均值和转换成一个String字符串均为聚合操作 + * 例如这里对int进行求和可以写成: + * + *
{@code
+	 *     Integer sum = integers.reduce(0, (a, b) -> a+b);
+	 * }
+ *

+ * 或者写成: + * + *

{@code
+	 *     Integer sum = integers.reduce(0, Integer::sum);
+	 * }
+ * + * @param identity 初始值,还用于限定泛型 + * @param accumulator 你想要的聚合操作 + * @return 聚合计算后的值 + */ + @Override + public T reduce(T identity, BinaryOperator accumulator) { + return stream.reduce(identity, accumulator); + } + + /** + * 对元素进行聚合,并返回聚合后用 {@link Optional}包裹的值,相当于在for循环里写sum=sum+ints[i] + * 该操作相当于: + *
{@code
+	 *     boolean foundAny = false;
+	 *     T result = null;
+	 *     for (T element : this stream) {
+	 *         if (!foundAny) {
+	 *             foundAny = true;
+	 *             result = element;
+	 *         }
+	 *         else
+	 *             result = accumulator.apply(result, element);
+	 *     }
+	 *     return foundAny ? Optional.of(result) : Optional.empty();
+	 * }
+ * 但它不局限于顺序执行,例如并行流等情况下 + * 这是一个终端操作
+ * 例如以下场景抛出 NPE : + *
{@code
+	 *      Optional reduce = Stream.builder().add(1).add(1).build().reduce((a, b) -> null);
+	 * }
+ * + * @param accumulator 你想要的聚合操作 + * @return 聚合后用 {@link Optional}包裹的值 + * @throws NullPointerException 如果给定的聚合操作中执行后结果为空,并用于下一次执行,则抛出该异常 + * @see #reduce(Object, BinaryOperator) + * @see #min(Comparator) + * @see #max(Comparator) + */ + @Override + public Optional reduce(BinaryOperator accumulator) { + return stream.reduce(accumulator); + } + + /** + * 对元素进行聚合,并返回聚合后的值,并行流时聚合拿到的初始值不稳定 + * 这是一个终端操作 + * + * @param identity 初始值 + * @param accumulator 累加器,具体为你要的聚合操作 + * @param combiner 用于并行流时组合多个结果 + * @param 初始值 + * @return 聚合操作的结果 + * @see #reduce(BinaryOperator) + * @see #reduce(Object, BinaryOperator) + */ + @Override + public U reduce(U identity, BiFunction accumulator, BinaryOperator combiner) { + return stream.reduce(identity, accumulator, combiner); + } + + /** + * 对元素进行收集,并返回收集后的容器 + * 这是一个终端操作 + * + * @param supplier 提供初始值的函数式接口,一般可以传入构造参数 + * @param accumulator 具体收集操作 + * @param combiner 用于并行流时组合多个结果 + * @param 用于收集元素的容器,大多是集合 + * @return 收集后的容器 + *
{@code
+	 *  List collect = Stream.iterate(1, i -> ++i).limit(10).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
+	 * }
+ */ + @Override + public R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) { + return stream.collect(supplier, accumulator, combiner); + } + + /** + * 对元素进行收集,并返回收集后的元素 + * 这是一个终端操作 + * + * @param collector 收集器 + * @param 容器类型 + * @param
具体操作时容器类型,例如 {@code List::add} 时它为 {@code List} + * @return 收集后的容器 + */ + @Override + public R collect(Collector collector) { + return stream.collect(collector); + } + + /** + * 获取最小值 + * + * @param comparator 一个用来比较大小的比较器{@link Comparator} + * @return 最小值 + */ + @Override + public Optional min(Comparator comparator) { + return stream.min(comparator); + } + + /** + * 获取最大值 + * + * @param comparator 一个用来比较大小的比较器{@link Comparator} + * @return 最大值 + */ + @Override + public Optional max(Comparator comparator) { + return stream.max(comparator); + } + + /** + * 返回流元素个数 + * + * @return 流元素个数 + */ + @Override + public long count() { + return stream.count(); + } + + /** + * 判断是否有任何一个元素满足给定断言 + * + * @param predicate 断言 + * @return 是否有任何一个元素满足给定断言 + */ + @Override + public boolean anyMatch(Predicate predicate) { + return stream.anyMatch(predicate); + } + + /** + * 判断是否所有元素满足给定断言 + * + * @param predicate 断言 + * @return 是否所有元素满足给定断言 + */ + @Override + public boolean allMatch(Predicate predicate) { + return stream.allMatch(predicate); + } + + /** + * 判断是否没有元素满足给定断言 + * + * @param predicate 断言 + * @return 是否没有元素满足给定断言 + */ + @Override + public boolean noneMatch(Predicate predicate) { + return stream.noneMatch(predicate); + } + + /** + * 获取第一个元素 + * + * @return 第一个元素 + */ + @Override + public Optional findFirst() { + return stream.findFirst(); + } + + /** + * 考虑性能,随便取一个,这里不是随机取一个,是随便取一个 + * + * @return 随便取一个 + */ + @Override + public Optional findAny() { + return stream.findAny(); + } + + /** + * 返回流的迭代器 + * + * @return 流的迭代器 + */ + @Override + public Iterator iterator() { + return stream.iterator(); + } + + /** + * 返回流的拆分器 + * + * @return 流的拆分器 + */ + @Override + public Spliterator spliterator() { + return stream.spliterator(); + } + + /** + * 返回流的并行状态 + * + * @return 流的并行状态 + */ + @Override + public boolean isParallel() { + return stream.isParallel(); + } + + /** + * 返回一个串行流,该方法可以将并行流转换为串行流 + * + * @return 串行流 + */ + @Override + public I sequential() { + return convertToStreamImpl(stream.sequential()); + } + + /** + * 将流转换为并行 + * + * @return 并行流 + */ + @Override + public I parallel() { + return convertToStreamImpl(stream.parallel()); + } + + /** + * 返回一个无序流(无手动排序) + *

标记一个流是不在意元素顺序的, 在并行流的某些情况下可以提高性能

+ * + * @return 无序流 + */ + @Override + public I unordered() { + return convertToStreamImpl(stream.unordered()); + } + + /** + * 在流关闭时执行操作 + * + * @param closeHandler 在流关闭时执行的操作 + * @return 流 + */ + @Override + public I onClose(Runnable closeHandler) { + return convertToStreamImpl(stream.onClose(closeHandler)); + } + + /** + * 关闭流 + * + * @see AutoCloseable#close() + */ + @Override + public void close() { + stream.close(); + } + + /** + * hashcode + * + * @return hashcode + */ + @Override + public int hashCode() { + return stream.hashCode(); + } + + /** + * equals + * + * @param obj 对象 + * @return 结果 + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof Stream) { + return stream.equals(obj); + } + return false; + } + + /** + * toString + * + * @return string + */ + @Override + public String toString() { + return stream.toString(); + } + + /** + * 根据一个原始的流,返回一个新包装类实例 + * + * @param stream 流 + * @return 实现类 + */ + protected abstract I convertToStreamImpl(Stream stream); + +} From 4893a5e9aa7ce38671d48f318a977e0fb313cbe3 Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 30 Aug 2022 15:04:30 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=AF=B9=E6=B5=81=E8=BF=9B=E8=A1=8C=E8=BD=AC=E6=8D=A2=E7=9A=84?= =?UTF-8?q?=E6=94=B6=E9=9B=86=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/stream/CollectorUtil.java | 114 ++++++++++++------ .../hutool/core/stream/CollectorUtilTest.java | 39 ++++-- 2 files changed, 107 insertions(+), 46 deletions(-) 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 47102cefd..50911a02d 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 @@ -4,15 +4,7 @@ import cn.hutool.core.lang.Opt; import cn.hutool.core.text.StrUtil; import cn.hutool.core.util.ArrayUtil; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.List; -import java.util.ArrayList; -import java.util.Objects; -import java.util.StringJoiner; +import java.util.*; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; @@ -32,7 +24,7 @@ public class CollectorUtil { * 说明已包含IDENTITY_FINISH特征 为 Characteristics.IDENTITY_FINISH 的缩写 */ public static final Set CH_ID - = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH)); + = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH)); /** * 说明不包含IDENTITY_FINISH特征 */ @@ -58,7 +50,7 @@ public class CollectorUtil { * @return {@link Collector} */ public static Collector joining(final CharSequence delimiter, - final Function toStringFunc) { + final Function toStringFunc) { return joining(delimiter, StrUtil.EMPTY, StrUtil.EMPTY, toStringFunc); } @@ -73,15 +65,15 @@ public class CollectorUtil { * @return {@link Collector} */ public static Collector joining(final CharSequence delimiter, - final CharSequence prefix, - final CharSequence suffix, - final Function toStringFunc) { + final CharSequence prefix, + final CharSequence suffix, + final Function toStringFunc) { return new SimpleCollector<>( - () -> new StringJoiner(delimiter, prefix, suffix), - (joiner, ele) -> joiner.add(toStringFunc.apply(ele)), - StringJoiner::merge, - StringJoiner::toString, - Collections.emptySet() + () -> new StringJoiner(delimiter, prefix, suffix), + (joiner, ele) -> joiner.add(toStringFunc.apply(ele)), + StringJoiner::merge, + StringJoiner::toString, + Collections.emptySet() ); } @@ -100,8 +92,8 @@ public class CollectorUtil { * @return {@link Collector} */ public static > Collector groupingBy(final Function classifier, - final Supplier mapFactory, - final Collector downstream) { + final Supplier mapFactory, + final Collector downstream) { final Supplier
downstreamSupplier = downstream.supplier(); final BiConsumer downstreamAccumulator = downstream.accumulator(); final BiConsumer, T> accumulator = (m, t) -> { @@ -141,7 +133,7 @@ public class CollectorUtil { */ public static Collector> groupingBy(final Function classifier, - final Collector downstream) { + final Collector downstream) { return groupingBy(classifier, HashMap::new, downstream); } @@ -171,8 +163,8 @@ public class CollectorUtil { */ public static Collector> toMap(final Function keyMapper, - final Function valueMapper, - final BinaryOperator mergeFunction) { + final Function valueMapper, + final BinaryOperator mergeFunction) { return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new); } @@ -191,11 +183,11 @@ public class CollectorUtil { */ public static > Collector toMap(final Function keyMapper, - final Function valueMapper, - final BinaryOperator mergeFunction, - final Supplier mapSupplier) { + final Function valueMapper, + final BinaryOperator mergeFunction, + final Supplier mapSupplier) { final BiConsumer accumulator - = (map, element) -> map.put(Opt.ofNullable(element).map(keyMapper).get(), Opt.ofNullable(element).map(valueMapper).get()); + = (map, element) -> map.put(Opt.ofNullable(element).map(keyMapper).get(), Opt.ofNullable(element).map(valueMapper).get()); return new SimpleCollector<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID); } @@ -241,15 +233,67 @@ public class CollectorUtil { */ public static >> Collector, ?, R> reduceListMap(final Supplier mapSupplier) { return Collectors.reducing(mapSupplier.get(), value -> { - R result = mapSupplier.get(); - value.forEach((k, v) -> result.computeIfAbsent(k, i -> new ArrayList<>()).add(v)); - return result; - }, (l, r) -> { - r.forEach((k, v) -> l.computeIfAbsent(k, i -> new ArrayList<>()).addAll(v)); - return l; - } + R result = mapSupplier.get(); + value.forEach((k, v) -> result.computeIfAbsent(k, i -> new ArrayList<>()).add(v)); + return result; + }, (l, r) -> { + r.forEach((k, v) -> l.computeIfAbsent(k, i -> new ArrayList<>()).addAll(v)); + return l; + } ); } + /** + * 将流转为{@link EasyStream} + * + * @param 输入元素类型 + * @return 收集器 + */ + public static Collector> toEasyStream() { + return transform(ArrayList::new, EasyStream::of); + } + + /** + * 收集元素,将其转为指定{@link Collection}集合后,再对该集合进行转换,并最终返回转换后的结果。 + * 返回的收集器的效果等同于: + *
{@code
+	 * 	Collection coll = Stream.of(a, b, c, d)
+	 * 		.collect(Collectors.toCollection(collFactory));
+	 * 	R result = mapper.apply(coll);
+	 * }
+ * + * @param collFactory 中间收集输入元素的集合的创建方法 + * @param mapper 最终将元素集合映射为返回值的方法 + * @param 返回值类型 + * @param 输入元素类型 + * @param 中间收集输入元素的集合类型 + * @return 收集器 + */ + public static > Collector transform( + Supplier collFactory, Function mapper) { + Objects.requireNonNull(collFactory); + Objects.requireNonNull(mapper); + return new SimpleCollector<>( + collFactory, C::add, (l1, l2) -> { l1.addAll(l2); return l1; }, mapper, CH_NOID + ); + } + + /** + * 收集元素,将其转为{@link ArrayList}集合后,再对该集合进行转换,并最终返回转换后的结果。 + * 返回的收集器的效果等同于: + *
{@code
+	 * 	List coll = Stream.of(a, b, c, d)
+	 * 		.collect(Collectors.toList());
+	 * 	R result = mapper.apply(coll);
+	 * }
+ * + * @param mapper 最终将元素集合映射为返回值的方法 + * @param 返回值类型 + * @param 输入元素类型 + * @return 收集器 + */ + public static Collector, R> transform(Function, R> mapper) { + return transform(ArrayList::new, mapper); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/stream/CollectorUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/stream/CollectorUtilTest.java index 570478082..068357369 100644 --- a/hutool-core/src/test/java/cn/hutool/core/stream/CollectorUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/stream/CollectorUtilTest.java @@ -4,11 +4,9 @@ import cn.hutool.core.map.MapUtil; import org.junit.Assert; import org.junit.Test; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * CollectorUtilTest @@ -21,17 +19,36 @@ public class CollectorUtilTest { @Test public void reduceListMapTest() { final Set> nameScoreMapList = StreamUtil.of( - // 集合内的第一个map,包含两个key value - MapUtil.builder("苏格拉底", 1).put("特拉叙马霍斯", 3).build(), - MapUtil.of("苏格拉底", 2), - MapUtil.of("特拉叙马霍斯", 1), - MapUtil.of("特拉叙马霍斯", 2) + // 集合内的第一个map,包含两个key value + MapUtil.builder("苏格拉底", 1).put("特拉叙马霍斯", 3).build(), + MapUtil.of("苏格拉底", 2), + MapUtil.of("特拉叙马霍斯", 1), + MapUtil.of("特拉叙马霍斯", 2) ).collect(Collectors.toSet()); // 执行聚合 final Map> nameScoresMap = nameScoreMapList.stream().collect(CollectorUtil.reduceListMap()); Assert.assertEquals(MapUtil.builder("苏格拉底", Arrays.asList(1, 2)) - .put("特拉叙马霍斯", Arrays.asList(3, 1, 2)).build(), - nameScoresMap); + .put("特拉叙马霍斯", Arrays.asList(3, 1, 2)).build(), + nameScoresMap); } + + @Test + public void testTransform() { + Stream stream = Stream.of(1, 2, 3, 4) + .collect(CollectorUtil.transform(EasyStream::of)); + Assert.assertEquals(EasyStream.class, stream.getClass()); + + stream = Stream.of(1, 2, 3, 4) + .collect(CollectorUtil.transform(HashSet::new, EasyStream::of)); + Assert.assertEquals(EasyStream.class, stream.getClass()); + } + + @Test + public void testToEasyStream() { + Stream stream =Stream.of(1, 2, 3, 4) + .collect(CollectorUtil.toEasyStream()); + Assert.assertEquals(EasyStream.class, stream.getClass()); + } + } From 95af26785520ecd8f1ab3c724953c4dea1cef249 Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 30 Aug 2022 15:05:00 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=94=AE=E5=80=BC?= =?UTF-8?q?=E5=AF=B9=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/stream/CollectorUtil.java | 17 + .../cn/hutool/core/stream/EntryStream.java | 774 ++++++++++++++++++ .../hutool/core/stream/CollectorUtilTest.java | 15 + .../hutool/core/stream/EntryStreamTest.java | 505 ++++++++++++ 4 files changed, 1311 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/core/stream/EntryStream.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/stream/EntryStreamTest.java 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 50911a02d..5e1fcefb3 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 @@ -243,6 +243,23 @@ public class CollectorUtil { ); } + /** + * 将流转为{@link EntryStream} + * + * @param keyMapper 键的映射方法 + * @param valueMapper 值的映射方法 + * @param 输入元素类型 + * @param 元素的键类型 + * @param 元素的值类型 + * @return 收集器 + */ + public static Collector, EntryStream> toEntryStream( + Function keyMapper, Function valueMapper) { + Objects.requireNonNull(keyMapper); + Objects.requireNonNull(valueMapper); + return transform(ArrayList::new, list -> EntryStream.of(list, keyMapper, valueMapper)); + } + /** * 将流转为{@link EasyStream} * diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/EntryStream.java b/hutool-core/src/main/java/cn/hutool/core/stream/EntryStream.java new file mode 100644 index 000000000..d97e74a3f --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/stream/EntryStream.java @@ -0,0 +1,774 @@ +package cn.hutool.core.stream; + +import cn.hutool.core.collection.ConcurrentHashSet; +import cn.hutool.core.map.multi.RowKeyTable; +import cn.hutool.core.map.multi.Table; +import cn.hutool.core.util.ObjUtil; + +import java.util.*; +import java.util.function.*; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + *
{@code
+	 *     FastStream ids = FastStream.of(users).flatMap(user -> FastStream.of(user.getId(), user.getParentId()));
+	 * }
+ * + * @param mapper 操作,返回流 + * @param 拆分后流的元素类型 + * @return 返回叠加拆分操作后的流 + */ + @Override + public EasyStream flatMap(Function, ? extends Stream> mapper) { + Objects.requireNonNull(mapper); + return EasyStream.of(stream.flatMap(mapper)); + } + + /** + *

将原有流的键执行mapper操作映射为流,流中的所有所有元素仍然对应原本的值, + * 然后再返回由这些流中所有元素组成的流新{@link EntryStream}串行流。
+ * 效果类似: + *

{@code
+	 * // stream = [{a = 1}, {b = 2}, {c = 3}]
+	 * stream.flatMapKey(key -> Stream.of(key + "1", key + "2"));
+	 * // stream = [{a1 = 1}, {a2 = 1}, {b1 = 2}, {b2 = 2}, {c1 = 3}, {c2 = 3}]
+	 * }
+ * + * @param keyMapper 值转映射方法 + * @param 新的键类型 + * @return 返回叠加拆分操作后的流 + */ + public EntryStream flatMapKey(Function> keyMapper) { + Objects.requireNonNull(keyMapper); + return new EntryStream<>( + stream.flatMap(e -> keyMapper + .apply(e.getKey()) + .map(newKey -> new Entry<>(newKey, e.getValue())) + ) + ); + } + + /** + *

将原有流的值执行mapper操作映射为流,流中的所有所有元素仍然对应原本的键, + * 然后再返回由这些流中所有元素组成的流新{@link EntryStream}串行流。
+ * 效果类似: + *

{@code
+	 * // stream = [{a = 1}, {b = 2}, {c = 3}]
+	 * stream.flatMapValue(num -> Stream.of(num, num+1));
+	 * // stream = [{a = 1}, {a = 2}, {b = 2}, {b = 3}, {c = 3}, {c = 4}]
+	 * }
+ * + * @param valueMapper 值转映射方法 + * @param 新的值类型 + * @return 返回叠加拆分操作后的流 + */ + public EntryStream flatMapValue(Function> valueMapper) { + Objects.requireNonNull(valueMapper); + return new EntryStream<>( + stream.flatMap(e -> valueMapper + .apply(e.getValue()) + .map(newVal -> new Entry<>(e.getKey(), newVal)) + ) + ); + } + + // ================================ 结束操作 ================================ + + /** + * 转为{@link Map}集合 + * + * @param mapFactory 获取集合的工厂方法 + * @param operator 当存在重复键时的处理 + * @return 集合 + * @see Collectors#toMap(Function, Function, BinaryOperator, Supplier) + */ + public Map toMap(Supplier> mapFactory, BinaryOperator operator) { + Objects.requireNonNull(mapFactory); + Objects.requireNonNull(operator); + return super.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, operator, mapFactory)); + } + + /** + * 转为{@link Map}集合 + * + * @param mapFactory 获取集合的工厂方法 + * @return 集合 + * @see Collectors#toMap(Function, Function, BinaryOperator) + */ + public Map toMap(Supplier> mapFactory) { + Objects.requireNonNull(mapFactory); + return super.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (t1, t2) -> t2, mapFactory)); + } + + /** + * 转为{@link HashMap}集合 + * + * @return 集合 + * @see Collectors#toMap(Function, Function) + * @throws IllegalArgumentException 当键重复时抛出 + */ + public Map toMap() { + return super.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + /** + * 将键值对分组后再转为二维{@link Map}集合,最终返回一个{@link Table}集合 + * + * @param rowKeyMapper 将键映射为父集合中键的方法 + * @param colMapFactory 创建子集合的工厂方法 + * @param operator 当存在重复键时的处理 + * @param 父集合的键类型 + * @return 集合 + * @see Collectors#groupingBy(Function, Supplier, Collector) + */ + public Table toTable( + BiFunction rowKeyMapper, Supplier> colMapFactory, BinaryOperator operator) { + Objects.requireNonNull(rowKeyMapper); + Objects.requireNonNull(colMapFactory); + Objects.requireNonNull(operator); + final Map> rawMap = collect(Collectors.groupingBy( + e -> rowKeyMapper.apply(e.getKey(), e.getValue()), + HashMap::new, + Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, operator, colMapFactory) + )); + return new RowKeyTable<>(rawMap, colMapFactory::get); + } + + /** + * 将键值对分组后再转为二维{@link HashMap}集合,最终返回一个{@link Table}集合 + * + * @param rowKeyMapper 创建父集合的工厂方法 + * @param 父集合的键类型 + * @return 集合 + * @throws IllegalArgumentException 当父集合或子集合中的键重复时抛出 + */ + public Table toTable(BiFunction rowKeyMapper) { + return toTable(rowKeyMapper, HashMap::new, throwingMerger()); + } + + /** + * 将键值对按值分组后再转为二维{@link Map}集合,最终返回一个{@link Table}集合 + * + * @param rowKeyMapper 将键映射为父集合中键的方法 + * @param colMapFactory 创建子集合的工厂方法 + * @param operator 当存在重复键时的处理 + * @param 父集合的键类型 + * @return 集合 + */ + public Table toTableByKey( + Function rowKeyMapper, Supplier> colMapFactory, BinaryOperator operator) { + return toTable((k, v) -> rowKeyMapper.apply(k), colMapFactory, operator); + } + + /** + * 将键值对按键分组后再转为二维{@link HashMap}集合,最终返回一个{@link Table}集合 + * + * @param rowKeyMapper 创建父集合的工厂方法 + * @param 父集合的键类型 + * @return 集合 + * @throws IllegalArgumentException 当父集合或子集合中的键重复时抛出 + */ + public Table toTableByKey(Function rowKeyMapper) { + return toTable((k, v) -> rowKeyMapper.apply(k)); + } + + /** + * 将键值对按值分组后再转为二维{@link Map}集合,最终返回一个{@link Table}集合 + * + * @param rowKeyMapper 将键映射为父集合中键的方法 + * @param colMapFactory 创建子集合的工厂方法 + * @param operator 当存在重复键时的处理 + * @param 父集合的键类型 + * @return 集合 + */ + public Table toTableByValue( + Function rowKeyMapper, Supplier> colMapFactory, BinaryOperator operator) { + return toTable((k, v) -> rowKeyMapper.apply(v), colMapFactory, operator); + } + + /** + * 将键值对按键分组后再转为二维{@link HashMap}集合,最终返回一个{@link Table}集合 + * + * @param rowKeyMapper 创建父集合的工厂方法 + * @param 父集合的键类型 + * @return 集合 + * @throws IllegalArgumentException 当父集合或子集合中的键重复时抛出 + */ + public Table toTableByValue(Function rowKeyMapper) { + return toTable((k, v) -> rowKeyMapper.apply(v)); + } + + /** + * 将键值对按键分组 + * + * @return 集合 + */ + public Map> groupByKey() { + return groupByKey(Collectors.toList()); + } + + /** + * 将键值对按键分组 + * + * @param collector 对具有相同键的值的收集器 + * @param 值集合的类型 + * @return 集合 + */ + public > Map groupByKey(Collector collector) { + return groupByKey((Supplier>)HashMap::new, collector); + } + + /** + * 将键值对按键分组 + * + * @param mapFactory 创建map集合的工厂方法 + * @param collector 对具有相同键的值的收集器 + * @param 值集合的类型 + * @param 返回的map集合类型 + * @return 集合 + */ + public , M extends Map> M groupByKey(Supplier mapFactory, Collector collector) { + return super.collect(Collectors.groupingBy( + Map.Entry::getKey, mapFactory, + CollectorUtil.transform(ArrayList::new, s -> s.stream().map(Map.Entry::getValue).collect(collector)) + )); + } + + /** + * 遍历键值对 + * + * @param consumer 操作 + */ + public void forEach(BiConsumer consumer) { + Objects.requireNonNull(consumer); + super.forEach(e -> consumer.accept(e.getKey(), e.getValue())); + } + + /** + * 将键值对翻转 + * + * @return {@link EntryStream}实例 + */ + public EntryStream inverse() { + return new EntryStream<>( + stream.map(e -> new Entry<>(e.getValue(), e.getKey())) + ); + } + + /** + * 收集键 + * + * @param collector 收集器 + * @param 返回值类型 + * @return 收集容器 + */ + public R collectKeys(Collector collector) { + return toKeyStream().collect(collector); + } + + /** + * 收集值 + * + * @param collector 收集器 + * @param 返回值类型 + * @return 收集容器 + */ + public R collectValues(Collector collector) { + return toValueStream().collect(collector); + } + + /** + * 是否存在任意符合条件的键值对 + * + * @param predicate 判断条件 + * @return 是否 + */ + public boolean anyMatch(BiPredicate predicate) { + return super.anyMatch(e -> predicate.test(e.getKey(), e.getValue())); + } + + /** + * 所有键值对是否都符合条件 + * + * @param predicate 判断条件 + * @return 是否 + */ + public boolean allMatch(BiPredicate predicate) { + Objects.requireNonNull(predicate); + return super.allMatch(e -> predicate.test(e.getKey(), e.getValue())); + } + + /** + * 所有键值对是否都不符合条件 + * + * @param predicate 判断条件 + * @return 是否 + */ + public boolean noneMatch(BiPredicate predicate) { + Objects.requireNonNull(predicate); + return super.noneMatch(e -> predicate.test(e.getKey(), e.getValue())); + } + + /** + * {@link Map.Entry}的基本实现 + */ + static class Entry implements Map.Entry { + + /** + * 键 + */ + private final K key; + + /** + * 值 + */ + private V val; + + /** + * 创建一个简单键值对对象 + * + * @param key 键 + * @param val 值 + */ + public Entry(K key, V val) { + this.key = key; + this.val = val; + } + + /** + * 创建一个简单键值对对象 + * + * @param entry 键值对 + */ + public Entry(Map.Entry entry) { + if (ObjUtil.isNull(entry)) { + this.key = null; + this.val = null; + } else { + this.key = entry.getKey(); + this.val = entry.getValue(); + } + } + + /** + * 获取键 + * + * @return 键 + */ + @Override + public K getKey() { + return key; + } + + /** + * 获取值 + * + * @return 值 + */ + @Override + public V getValue() { + return val; + } + + /** + * 设置值 + * + * @param value 值 + * @return 旧值 + */ + @Override + public V setValue(V value) { + V old = val; + val = value; + return old; + } + + @Override + public String toString() { + return "{" + key + "=" + val + '}'; + } + + } + + /** + * key重复时直接抛出异常 + */ + private static BinaryOperator throwingMerger() { + return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }; + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/stream/CollectorUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/stream/CollectorUtilTest.java index 068357369..00670f8d2 100644 --- a/hutool-core/src/test/java/cn/hutool/core/stream/CollectorUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/stream/CollectorUtilTest.java @@ -5,6 +5,7 @@ import org.junit.Assert; import org.junit.Test; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -51,4 +52,18 @@ public class CollectorUtilTest { Assert.assertEquals(EasyStream.class, stream.getClass()); } + @Test + public void testToEntryStream() { + Map map = Stream.of(1, 2, 3, 4, 5) + // 转为EntryStream + .collect(CollectorUtil.toEntryStream(Function.identity(), String::valueOf)) + // 过滤偶数 + .filterByKey(k -> (k & 1) == 1) + .inverse() + .toMap(); + Assert.assertEquals((Integer)1, map.get("1")); + Assert.assertEquals((Integer)3, map.get("3")); + Assert.assertEquals((Integer)5, map.get("5")); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/stream/EntryStreamTest.java b/hutool-core/src/test/java/cn/hutool/core/stream/EntryStreamTest.java new file mode 100644 index 000000000..3bbfe072c --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/stream/EntryStreamTest.java @@ -0,0 +1,505 @@ +package cn.hutool.core.stream; + +import cn.hutool.core.map.multi.Table; +import org.junit.Assert; +import org.junit.Test; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class EntryStreamTest { + + @Test + public void testMerge() { + Assert.assertEquals(0, EntryStream.merge(null, null).count()); + Assert.assertEquals( + Arrays.asList(2, 4, 6), + EntryStream.merge(Arrays.asList(1, 2, 3), Arrays.asList(1, 2, 3)) + .map(Integer::sum) + .toList() + ); + Assert.assertEquals( + Arrays.asList(1, 2, null), + EntryStream.merge(Arrays.asList(1, 2, 3), Arrays.asList(1, 2)) + .collectValues(Collectors.toList()) + ); + Assert.assertEquals( + Arrays.asList(1, 2, null), + EntryStream.merge(Arrays.asList(1, 2), Arrays.asList(1, 2, 3)) + .collectKeys(Collectors.toList()) + ); + } + + @Test + public void testOf() { + Map map = new HashMap<>(); + map.put("1", "1"); + Assert.assertEquals(1, EntryStream.of(map).count()); + + Set> entries = new HashSet<>(); + entries.add(new Entry<>(1, 1)); + entries.add(null); + Assert.assertEquals(2, EntryStream.of(entries).count()); + Assert.assertEquals(2, EntryStream.of(entries.stream()).count()); + + Iterable iterable = Arrays.asList(1, 2, null); + Assert.assertEquals(3, EntryStream.of(iterable, Function.identity(), Function.identity()).count()); + } + + @Test + public void testEmpty() { + Assert.assertEquals(0, EntryStream.empty().count()); + } + + @Test + public void testDistinctByKey() { + long count = EntryStream.of(Arrays.asList(new Entry<>(1, 1), new Entry<>(1, 2), new Entry<>(2, 1), new Entry<>(2, 2))) + .distinctByKey() + .count(); + Assert.assertEquals(2, count); + } + + @Test + public void testDistinctByValue() { + long count = EntryStream.of(Arrays.asList(new Entry<>(1, 1), new Entry<>(1, 2), new Entry<>(2, 1), new Entry<>(2, 2))) + .distinctByValue() + .count(); + Assert.assertEquals(2, count); + } + + @Test + public void testFilter() { + long count = EntryStream.of(Arrays.asList(new Entry<>(1, 1), new Entry<>(1, 2), new Entry<>(2, 1), new Entry<>(2, 2))) + .filter((k, v) -> k == 1 && v == 1) + .count(); + Assert.assertEquals(1, count); + } + + @Test + public void testFilterByKey() { + long count = EntryStream.of(Arrays.asList(new Entry<>(1, 1), new Entry<>(1, 2), new Entry<>(2, 1), new Entry<>(2, 2))) + .filterByKey(k -> k == 1) + .count(); + Assert.assertEquals(2, count); + } + + @Test + public void testFilterByValue() { + long count = EntryStream.of(Arrays.asList(new Entry<>(1, 1), new Entry<>(1, 2), new Entry<>(2, 1), new Entry<>(2, 2))) + .filterByValue(v -> v == 1) + .count(); + Assert.assertEquals(2, count); + } + + @Test + public void testPeekKey() { + List keys = new ArrayList<>(); + EntryStream.of(Arrays.asList(new Entry<>(1, 1), new Entry<>(1, 2), new Entry<>(2, 1), new Entry<>(2, 2))) + .peekKey(keys::add) + .count(); + Assert.assertEquals(Arrays.asList(1, 1, 2, 2), keys); + } + + @Test + public void testPeekValue() { + List values = new ArrayList<>(); + EntryStream.of(Arrays.asList(new Entry<>(1, 1), new Entry<>(1, 2), new Entry<>(2, 1), new Entry<>(2, 2))) + .peekValue(values::add) + .count(); + Assert.assertEquals(Arrays.asList(1, 2, 1, 2), values); + } + + @Test + public void testPush() { + Assert.assertEquals( + 5, + EntryStream.of(Arrays.asList(1, 2, 3), Function.identity(), Function.identity()) + .push(4, 4) + .push(5, 5) + .count() + ); + + } + + @Test + public void testSortByKey() { + List> entries = EntryStream.of(Arrays.asList(new Entry<>(3, 1), new Entry<>(2, 1), new Entry<>(4, 1), new Entry<>(1, 1))) + .sortByKey(Comparator.comparingInt(Integer::intValue)) + .collect(Collectors.toList()); + Assert.assertEquals( + Arrays.asList(1, 2, 3, 4), + entries.stream().map(Map.Entry::getKey).collect(Collectors.toList()) + ); + } + + @Test + public void testSortByValue() { + List> entries = EntryStream.of(Arrays.asList(new Entry<>(4, 4), new Entry<>(2, 2), new Entry<>(1, 1), new Entry<>(3, 3))) + .sortByValue(Comparator.comparingInt(Integer::intValue)) + .collect(Collectors.toList()); + Assert.assertEquals( + Arrays.asList(1, 2, 3, 4), + entries.stream().map(Map.Entry::getValue).collect(Collectors.toList()) + ); + } + + @Test + public void testToValueStream() { + Map map = new HashMap<>(); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + Assert.assertEquals( + new ArrayList<>(map.values()), EntryStream.of(map).toValueStream().collect(Collectors.toList()) + ); + } + + @Test + public void testToKeyStream() { + Map map = new HashMap<>(); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + Assert.assertEquals( + new ArrayList<>(map.keySet()), EntryStream.of(map).toKeyStream().collect(Collectors.toList()) + ); + } + + @Test + public void testCollectKey() { + Map map = new HashMap<>(); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + List keys = EntryStream.of(map).collectKeys(Collectors.toList()); + Assert.assertEquals(new ArrayList<>(map.keySet()), keys); + } + + @Test + public void testCollectValue() { + Map map = new HashMap<>(); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + List keys = EntryStream.of(map).collectValues(Collectors.toList()); + Assert.assertEquals(new ArrayList<>(map.keySet()), keys); + } + + @Test + public void testMapKeys() { + Map map = new HashMap<>(); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + Assert.assertEquals( + Arrays.asList("1", "2", "3"), + EntryStream.of(map) + .mapKeys(String::valueOf) + .toKeyStream() + .collect(Collectors.toList()) + ); + } + + @Test + public void testMapValues() { + Map map = new HashMap<>(); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + Assert.assertEquals( + Arrays.asList("1", "2", "3"), + EntryStream.of(map) + .mapValues(String::valueOf) + .toValueStream() + .collect(Collectors.toList()) + ); + } + + @Test + public void testMap() { + Map map = new HashMap<>(); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + Assert.assertEquals( + Arrays.asList("11", "22", "33"), + EntryStream.of(map) + .map((k, v) -> k.toString() + v.toString()) + .collect(Collectors.toList()) + ); + } + + @Test + public void testFlatMap() { + Map map = new HashMap<>(); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + List list = EntryStream.of(map) + .flatMap(e -> Stream.of(e.getKey(), e.getKey() + 1)) + .collect(Collectors.toList()); + Assert.assertEquals(Arrays.asList(1, 2, 2, 3, 3, 4), list); + } + + @Test + public void testFlatMapValue() { + Map map = new HashMap<>(); + map.put("class1", 1); + map.put("class2", 2); + map.put("class3", 3); + List values = EntryStream.of(map) + .flatMapKey(k -> Stream.of(k + "'s student1", k + "'s student2")) + .map((k, v) -> v + "=" + k) + .sorted() + .collect(Collectors.toList()); + Assert.assertEquals( + Arrays.asList( + "1=class1's student1", "1=class1's student2", + "2=class2's student1", "2=class2's student2", + "3=class3's student1", "3=class3's student2" + ), + values + ); + } + + @Test + public void testInverse() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + map.put("key2", "value2"); + map.put("key3", "value3"); + List results = EntryStream.of(map) + .inverse() + .map((k, v) -> k + "=" + v) + .collect(Collectors.toList()); + Assert.assertEquals( + Arrays.asList("value1=key1", "value2=key2", "value3=key3"), + results + ); + } + + @Test + public void testFlatMapKey() { + Map map = new HashMap<>(); + map.put(1, "class1"); + map.put(2, "class2"); + map.put(3, "class3"); + List values = EntryStream.of(map) + .flatMapValue(v -> Stream.of(v + "'s student1", v + "'s student2")) + .map((k, v) -> k + "=" + v) + .collect(Collectors.toList()); + Assert.assertEquals( + Arrays.asList( + "1=class1's student1", "1=class1's student2", + "2=class2's student1", "2=class2's student2", + "3=class3's student1", "3=class3's student2" + ), + values + ); + } + + @Test + public void testForEach() { + Map map = new HashMap<>(); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + + List keys = new ArrayList<>(); + List values = new ArrayList<>(); + EntryStream.of(map).forEach((k ,v) -> { + keys.add(k); + values.add(v); + }); + Assert.assertEquals(Arrays.asList(1, 2, 3), keys); + Assert.assertEquals(Arrays.asList(1, 2, 3), values); + } + + @Test + public void testToMap() { + Map map = new HashMap<>(); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + + Map result = EntryStream.of(map).toMap(); + Assert.assertEquals(map, result); + + result = EntryStream.of(map).toMap(LinkedHashMap::new); + Assert.assertEquals(new LinkedHashMap<>(map), result); + + result = EntryStream.of(map).toMap(LinkedHashMap::new, (t1, t2) -> t1); + Assert.assertEquals(new LinkedHashMap<>(map), result); + } + + @Test + public void testToTable() { + Map map = new HashMap<>(); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + map.put(4, 4); + + // 按是否偶数分组 + Table table = EntryStream.of(map).toTable( + (k ,v) -> (k & 1) == 0, HashMap::new, (t1, t2) -> t1 + ); + Assert.assertEquals((Integer)1, table.get(false, 1)); + Assert.assertEquals((Integer)3, table.get(false, 3)); + Assert.assertEquals((Integer)2, table.get(true, 2)); + Assert.assertEquals((Integer)4, table.get(true, 4)); + + table = EntryStream.of(map).toTable((k ,v) -> (k & 1) == 0); + Assert.assertEquals((Integer)1, table.get(false, 1)); + Assert.assertEquals((Integer)3, table.get(false, 3)); + Assert.assertEquals((Integer)2, table.get(true, 2)); + Assert.assertEquals((Integer)4, table.get(true, 4)); + } + + @Test + public void testToTableByKey() { + Map map = new HashMap<>(); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + map.put(4, 4); + + // 按是否偶数分组 + Table table = EntryStream.of(map).toTableByKey( + k -> (k & 1) == 0, HashMap::new, (t1, t2) -> t1 + ); + Assert.assertEquals((Integer)1, table.get(false, 1)); + Assert.assertEquals((Integer)3, table.get(false, 3)); + Assert.assertEquals((Integer)2, table.get(true, 2)); + Assert.assertEquals((Integer)4, table.get(true, 4)); + + table = EntryStream.of(map).toTableByKey(k -> (k & 1) == 0); + Assert.assertEquals((Integer)1, table.get(false, 1)); + Assert.assertEquals((Integer)3, table.get(false, 3)); + Assert.assertEquals((Integer)2, table.get(true, 2)); + Assert.assertEquals((Integer)4, table.get(true, 4)); + } + + @Test + public void testToTableByValue() { + Map map = new HashMap<>(); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + map.put(4, 4); + + // 按是否偶数分组 + Table table = EntryStream.of(map).toTableByValue( + v -> (v & 1) == 0, HashMap::new, (t1, t2) -> t1 + ); + Assert.assertEquals((Integer)1, table.get(false, 1)); + Assert.assertEquals((Integer)3, table.get(false, 3)); + Assert.assertEquals((Integer)2, table.get(true, 2)); + Assert.assertEquals((Integer)4, table.get(true, 4)); + + table = EntryStream.of(map).toTableByValue(v -> (v & 1) == 0); + Assert.assertEquals((Integer)1, table.get(false, 1)); + Assert.assertEquals((Integer)3, table.get(false, 3)); + Assert.assertEquals((Integer)2, table.get(true, 2)); + Assert.assertEquals((Integer)4, table.get(true, 4)); + } + + @Test + public void testGroupByKey() { + Map> map1 = EntryStream.of(Arrays.asList(1, 1, 2, 2), Function.identity(), Function.identity()) + .groupByKey(); + Assert.assertEquals(2, map1.size()); + Assert.assertEquals(Arrays.asList(1, 1), map1.get(1)); + Assert.assertEquals(Arrays.asList(2, 2), map1.get(2)); + + Map> map2 = EntryStream.of(Arrays.asList(1, 1, 2, 2), Function.identity(), Function.identity()) + .groupByKey(Collectors.toSet()); + Assert.assertEquals(2, map2.size()); + Assert.assertEquals(Collections.singleton(1), map2.get(1)); + Assert.assertEquals(Collections.singleton(2), map2.get(2)); + + Map> map3 = EntryStream.of(Arrays.asList(1, 1, 2, 2), Function.identity(), Function.identity()) + .groupByKey(LinkedHashMap::new, Collectors.toSet()); + Assert.assertEquals(2, map3.size()); + Assert.assertEquals(Collections.singleton(1), map3.get(1)); + Assert.assertEquals(Collections.singleton(2), map3.get(2)); + } + + @Test + public void testAnyMatch() { + Assert.assertTrue(EntryStream.of(Arrays.asList(1, 1, 2, 2), Function.identity(), Function.identity()) + .anyMatch((k, v) -> (k & 1) == 1)); + Assert.assertFalse(EntryStream.of(Arrays.asList(2, 2, 2, 2), Function.identity(), Function.identity()) + .anyMatch((k, v) -> (k & 1) == 1)); + } + + @Test + public void testAllMatch() { + Assert.assertFalse(EntryStream.of(Arrays.asList(1, 1, 2, 2), Function.identity(), Function.identity()) + .allMatch((k, v) -> (k & 1) == 1)); + Assert.assertTrue(EntryStream.of(Arrays.asList(2, 2, 2, 2), Function.identity(), Function.identity()) + .allMatch((k, v) -> (k & 1) == 0)); + } + + @Test + public void testNoneMatch() { + Assert.assertFalse(EntryStream.of(Arrays.asList(1, 1, 2, 2), Function.identity(), Function.identity()) + .noneMatch((k, v) -> (k & 1) == 1)); + Assert.assertTrue(EntryStream.of(Arrays.asList(2, 2, 2, 2), Function.identity(), Function.identity()) + .noneMatch((k, v) -> (k & 1) == 1)); + } + + @Test + public void testNonNull() { + Map map = new HashMap<>(); + map.put(1, null); + map.put(null, 1); + Assert.assertEquals(0, EntryStream.of(map).nonNull().count()); + } + + @Test + public void testKeyNonNull() { + Map map = new HashMap<>(); + map.put(1, null); + map.put(null, 1); + Assert.assertEquals(1, EntryStream.of(map).keyNonNull().count()); + } + + @Test + public void testValueNonNull() { + Map map = new HashMap<>(); + map.put(1, null); + map.put(null, 1); + Assert.assertEquals(1, EntryStream.of(map).valueNonNull().count()); + } + + private static class Entry implements Map.Entry { + + private final K key; + private final V value; + + public Entry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public V setValue(V value) { + return null; + } + } + +} +

针对键值对对象{@link Map.Entry}特化的增强流, + * 本身可视为一个元素类型为{@link Map.Entry}的{@link Stream}。
+ * 用于支持流式处理{@link Map}集合中的、或具有潜在可能转为{@link Map}集合的数据。 + * + * @param 键类型 + * @param 值类型 + * @author huangchengxing + */ +public class EntryStream extends StreamWrapper, EntryStream> { + + /** + * 根据键与值的集合创建键值对流,若两集合在相同下标的位置找不到对应的键或值,则使用{@code null}填充。
+ * 比如: {@code [1, 2, 3]}与{@code [1, 2]}合并,则得到{@code [{1=1}, {2=2}, {3=null}]}。 + * + * @param keys 键集合 + * @param values 值集合 + * @return {@link EntryStream}实例 + */ + public static EntryStream merge(Iterable
keys, Iterable values) { + final boolean hasKeys = ObjUtil.isNotNull(keys); + final boolean hasValues = ObjUtil.isNotNull(values); + // 皆为空 + if (!hasKeys && !hasValues) { + return empty(); + } + // 值为空 + if (hasKeys && !hasValues) { + return of(keys, Function.identity(), k -> null); + } + // 键为空 + if (!hasKeys) { + return of(values, v -> null, Function.identity()); + } + // 皆不为空 + final List> entries = new ArrayList<>(); + final Iterator keyItr = keys.iterator(); + final Iterator valueItr = values.iterator(); + while (keyItr.hasNext() || valueItr.hasNext()) { + entries.add(new Entry<>( + keyItr.hasNext() ? keyItr.next() : null, + valueItr.hasNext() ? valueItr.next() : null + )); + } + return of(entries); + } + + /** + * 根据一个{@link Map}集合中的键值对创建一个串行流, + * 对流的操作不会影响到入参的{@code map}实例本身 + * + * @param map 集合 + * @param 键类型 + * @param 值类型 + * @return {@link EntryStream}实例 + */ + public static EntryStream of(Map map) { + return ObjUtil.isNull(map) ? + empty() : of(map.entrySet()); + } + + /** + * 根据一个{@link Map.Entry}类型的{@link Iterable}创建一个串行流, + * 对流的操作不会影响到入参的{@code entries}实例本身。
+ * 若输入流中存在元素为{@code null},则会映射为一个键值皆为{@code null}的键值对。 + * + * @param entries {@link Iterable}实例 + * @param
键类型 + * @param 值类型 + * @return {@link EntryStream}实例 + */ + public static EntryStream of(Iterable> entries) { + return ObjUtil.isNull(entries) ? + empty() : of(StreamSupport.stream(entries.spliterator(), false)); + } + + /** + * 根据一个{@link Collection}集合中创建一个串行流 + * + * @param source 原始集合 + * @param keyMapper 键的映射方法 + * @param valueMapper 值的映射方法 + * @param 键类型 + * @param 值类型 + * @return {@link EntryStream}实例 + */ + public static EntryStream of( + Iterable source, Function keyMapper, Function valueMapper) { + Objects.requireNonNull(keyMapper); + Objects.requireNonNull(valueMapper); + if (ObjUtil.isNull(source)) { + return empty(); + } + final Stream> stream = StreamSupport.stream(source.spliterator(), false) + .map(t -> new Entry<>(keyMapper.apply(t), valueMapper.apply(t))); + return new EntryStream<>(stream); + } + + /** + * 包装一个已有的流,若入参为空则返回一个空的串行流。
+ * 若输入流中存在元素为{@code null},则会映射为一个键值皆为{@code null}的键值对。 + * + * @param stream 流 + * @param
键类型 + * @param 值类型 + * @return {@link EntryStream}实例 + */ + public static EntryStream of(Stream> stream) { + return ObjUtil.isNull(stream) ? + empty() : new EntryStream<>(stream.map(Entry::new)); + } + + /** + * 创建一个空的串行流 + * + * @param 键类型 + * @param 值类型 + * @return {@link EntryStream}实例 + */ + public static EntryStream empty() { + return new EntryStream<>(Stream.empty()); + } + + /** + * 构造 + */ + EntryStream(Stream> stream) { + super(stream); + } + + // ================================ override ================================ + + /** + * 根据一个原始的流,返回一个新包装类实例 + * + * @param stream 流 + * @return 实现类 + */ + @Override + protected EntryStream convertToStreamImpl(Stream> stream) { + return new EntryStream<>(stream); + } + + // ================================ 中间操作 ================================ + + /** + * 根据键去重,默认丢弃靠后的 + * + * @return {@link EntryStream}实例 + */ + public EntryStream distinctByKey() { + Set accessed = new ConcurrentHashSet<>(16); + return new EntryStream<>(stream.filter(e -> { + K key = e.getKey(); + if (accessed.contains(key)) { + return false; + } + accessed.add(key); + return true; + })); + } + + /** + * 根据值去重,默认丢弃靠后的 + * + * @return {@link EntryStream}实例 + */ + public EntryStream distinctByValue() { + Set accessed = new ConcurrentHashSet<>(16); + return new EntryStream<>(stream.filter(e -> { + V val = e.getValue(); + if (accessed.contains(val)) { + return false; + } + accessed.add(val); + return true; + })); + } + + /** + * 根据键和值过滤键值对 + * + * @param filter 判断条件 + * @return {@link EntryStream}实例 + */ + public EntryStream filter(BiPredicate filter) { + Objects.requireNonNull(filter); + return super.filter(e -> filter.test(e.getKey(), e.getValue())); + } + + /** + * 根据键过滤键值对 + * + * @param filter 判断条件 + * @return {@link EntryStream}实例 + */ + public EntryStream filterByKey(Predicate filter) { + Objects.requireNonNull(filter); + return super.filter(e -> filter.test(e.getKey())); + } + + /** + * 根据值过滤键值对 + * + * @param filter 判断条件 + * @return {@link EntryStream}实例 + */ + public EntryStream filterByValue(Predicate filter) { + Objects.requireNonNull(filter); + return super.filter(e -> filter.test(e.getValue())); + } + + /** + * 过滤流中键值对本身、键值对中的值或键为{@code null}的元素 + * + * @return {@link EntryStream}实例 + */ + public EntryStream nonNull() { + return super.filter(e -> ObjUtil.isNotNull(e) && ObjUtil.isNotNull(e.getKey()) && ObjUtil.isNotNull(e.getValue())); + } + + /** + * 过滤流中键值对本身,或键值对的键为{@code null}的元素 + * + * @return {@link EntryStream}实例 + */ + public EntryStream keyNonNull() { + return super.filter(e -> ObjUtil.isNotNull(e) && ObjUtil.isNotNull(e.getKey())); + } + + /** + * 过滤流中键值对本身,或键值对的值为{@code null}的元素 + * + * @return {@link EntryStream}实例 + */ + public EntryStream valueNonNull() { + return super.filter(e -> ObjUtil.isNotNull(e) && ObjUtil.isNotNull(e.getValue())); + } + + /** + * 检查键 + * + * @param consumer 操作 + * @return {@link EntryStream}实例 + */ + public EntryStream peekKey(Consumer consumer) { + Objects.requireNonNull(consumer); + return super.peek(e -> consumer.accept(e.getKey())); + } + + /** + * 检查值 + * + * @param consumer 操作 + * @return {@link EntryStream}实例 + */ + public EntryStream peekValue(Consumer consumer) { + Objects.requireNonNull(consumer); + return super.peek(e -> consumer.accept(e.getValue())); + } + + /** + * 根据键排序 + * + * @param comparator 排序器 + * @return {@link EntryStream}实例 + */ + public EntryStream sortByKey(Comparator comparator) { + Objects.requireNonNull(comparator); + return sorted(Map.Entry.comparingByKey(comparator)); + } + + /** + * 根据值排序 + * + * @param comparator 排序器 + * @return {@link EntryStream}实例 + */ + public EntryStream sortByValue(Comparator comparator) { + Objects.requireNonNull(comparator); + return sorted(Map.Entry.comparingByValue(comparator)); + } + + // ================================ 转换操作 ================================ + + /** + * 向当前流末尾追加元素 + * + * @param key 键 + * @param value 值 + * @return {@link EntryStream}实例 + */ + public EntryStream push(K key, V value) { + return new EntryStream<>(Stream.concat(stream, Stream.of(new Entry<>(key, value)))); + } + + /** + * 转为值的流 + * + * @return 值的流 + */ + public EasyStream toValueStream() { + return EasyStream.of(stream.map(Map.Entry::getValue)); + } + + /** + * 转为键的流 + * + * @return 值的流 + */ + public EasyStream toKeyStream() { + return EasyStream.of(stream.map(Map.Entry::getKey)); + } + + /** + * 将键映射为另一类型 + * + * @param mapper 映射方法 + * @param 新的键类型 + * @return {@link EntryStream}实例 + */ + public EntryStream mapKeys(Function mapper) { + Objects.requireNonNull(mapper); + return new EntryStream<>( + stream.map(e -> new Entry<>(mapper.apply(e.getKey()), e.getValue())) + ); + } + + /** + * 将值映射为另一类型 + * + * @param mapper 映射方法 + * @param 新的值类型 + * @return {@link EntryStream}实例 + */ + public EntryStream mapValues(Function mapper) { + Objects.requireNonNull(mapper); + return new EntryStream<>( + stream.map(e -> new Entry<>(e.getKey(), mapper.apply(e.getValue()))) + ); + } + + /** + * 返回与指定函数将元素作为参数执行的结果组成的流 + * 这是一个无状态中间操作 + * + * @param mapper 指定的函数 + * @param 函数执行后返回流中元素的类型 + * @return 返回叠加操作后的流 + */ + @Override + public EasyStream map(Function, ? extends R> mapper) { + Objects.requireNonNull(mapper); + return EasyStream.of(stream.map(mapper)); + } + + /** + * 将实例转为根据键值对生成的单对象{@link Stream}实例 + * + * @param mapper 映射方法 + * @param 函数执行后返回流中元素的类型 + * @return 映射后的单对象组成的流 + */ + public EasyStream map(BiFunction mapper) { + Objects.requireNonNull(mapper); + return EasyStream.of(stream.map(e -> mapper.apply(e.getKey(), e.getValue()))); + } + + /** + * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作,返回多个流所有元素组成的流
+ * 这是一个无状态中间操作
+ * 例如,将users里所有user的id和parentId组合在一起,形成一个新的流: + *