From 741f0aa53a08f81f8d3732c011209baa059d8a4f Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Tue, 6 Sep 2022 11:21:38 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=A2=9E=E5=BC=BA=E6=B5=81?= =?UTF-8?q?=EF=BC=9A=201.=E7=A7=BB=E9=99=A4SimpleStreamWrapper;=202.?= =?UTF-8?q?=E5=B0=86EasyStream=E4=B8=AD=E9=83=A8=E5=88=86=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E7=A7=BB=E5=8A=A8=E8=87=B3=E6=8E=A5=E5=8F=A3=E4=B8=AD?= =?UTF-8?q?;=203.=E8=B0=83=E6=95=B4=E7=B1=BB=E5=90=8D=E3=80=81=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=90=8D=E3=80=81=E5=8F=98=E9=87=8F=E5=90=8D=E4=B8=8E?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=B3=A8=E9=87=8A;=204.=E4=B8=BA=E5=AE=9E?= =?UTF-8?q?=E4=BE=8B=E6=96=B9=E6=B3=95=E6=B7=BB=E5=8A=A0=E7=A9=BA=E5=80=BC?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C;=205.=E8=A1=A5=E5=85=85=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=94=A8=E4=BE=8B;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stream/AbstractEnhancedStreamWrapper.java | 40 - .../stream/AbstractEnhancedWrappedStream.java | 80 ++ .../cn/hutool/core/stream/CollectorUtil.java | 24 +- .../cn/hutool/core/stream/EasyStream.java | 319 +------- .../cn/hutool/core/stream/EntryStream.java | 22 +- .../core/stream/SimpleStreamWrapper.java | 55 -- ...pper.java => TerminableWrappedStream.java} | 390 +++++----- .../stream/TransformableStreamWrapper.java | 216 ------ .../stream/TransformableWrappedStream.java | 570 ++++++++++++++ ...{StreamWrapper.java => WrappedStream.java} | 91 ++- .../AbstractEnhancedWrappedStreamTest.java | 705 ++++++++++++++++++ .../cn/hutool/core/stream/EasyStreamTest.java | 58 +- .../hutool/core/stream/EntryStreamTest.java | 87 ++- 13 files changed, 1787 insertions(+), 870 deletions(-) delete mode 100644 hutool-core/src/main/java/cn/hutool/core/stream/AbstractEnhancedStreamWrapper.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/stream/AbstractEnhancedWrappedStream.java delete mode 100644 hutool-core/src/main/java/cn/hutool/core/stream/SimpleStreamWrapper.java rename hutool-core/src/main/java/cn/hutool/core/stream/{TerminableStreamWrapper.java => TerminableWrappedStream.java} (59%) delete mode 100644 hutool-core/src/main/java/cn/hutool/core/stream/TransformableStreamWrapper.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/stream/TransformableWrappedStream.java rename hutool-core/src/main/java/cn/hutool/core/stream/{StreamWrapper.java => WrappedStream.java} (87%) create mode 100644 hutool-core/src/test/java/cn/hutool/core/stream/AbstractEnhancedWrappedStreamTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/AbstractEnhancedStreamWrapper.java b/hutool-core/src/main/java/cn/hutool/core/stream/AbstractEnhancedStreamWrapper.java deleted file mode 100644 index 02a9a6789..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/stream/AbstractEnhancedStreamWrapper.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.hutool.core.stream; - -import java.util.Objects; -import java.util.stream.Stream; - -/** - * {@link StreamWrapper}的基本实现,用于包装一个已有的流实例, - * 使其支持相对原生{@link Stream}更多的中间操作与终端操作。 - * - * @author huangchengxing - * @see EasyStream - * @see EntryStream - */ -public abstract class AbstractEnhancedStreamWrapper> - implements TerminableStreamWrapper, TransformableStreamWrapper { - - /** - * 原始的流实例 - */ - protected final Stream stream; - - /** - * 获取被包装的元素流实例 - */ - @Override - public Stream stream() { - return stream; - } - - /** - * 创建一个流包装器 - * - * @param stream 包装的流对象 - * @throws NullPointerException 当{@code stream}为{@code null}时抛出 - */ - protected AbstractEnhancedStreamWrapper(Stream stream) { - this.stream = Objects.requireNonNull(stream, "stream must not null"); - } - -} diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/AbstractEnhancedWrappedStream.java b/hutool-core/src/main/java/cn/hutool/core/stream/AbstractEnhancedWrappedStream.java new file mode 100644 index 000000000..5fc66d653 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/stream/AbstractEnhancedWrappedStream.java @@ -0,0 +1,80 @@ +package cn.hutool.core.stream; + +import java.util.Objects; +import java.util.stream.Stream; + +/** + * {@link WrappedStream}接口的公共实现,用于包装并增强一个已有的流实例 + * + * @param 流中的元素类型 + * @param {@link AbstractEnhancedWrappedStream}的实现类类型 + * @author huangchengxing + * @see EasyStream + * @see EntryStream + * @since 6.0.0 + */ +public abstract class AbstractEnhancedWrappedStream> + implements TerminableWrappedStream, TransformableWrappedStream { + + /** + * 原始的流实例 + */ + protected final Stream stream; + + /** + * 获取被包装的元素流实例 + */ + @Override + public Stream stream() { + return stream; + } + + /** + * 创建一个流包装器 + * + * @param stream 包装的流对象 + * @throws NullPointerException 当{@code stream}为{@code null}时抛出 + */ + protected AbstractEnhancedWrappedStream(Stream stream) { + this.stream = Objects.requireNonNull(stream, "stream must not null"); + } + + /** + * 获取当前被包装的实例的哈希值 + * + * @return 哈希值 + */ + @Override + public int hashCode() { + return stream.hashCode(); + } + + /** + * 比较被包装的实例是否相等 + * + * @param obj 对象 + * @return 是否相等 + */ + @Override + public boolean equals(Object obj) { + return obj instanceof Stream && stream.equals(obj); + } + + /** + * 将被包装的实例转为字符串 + * + * @return 字符串 + */ + @Override + public String toString() { + return stream.toString(); + } + + /** + * 触发流的执行,这是一个终端操作 + */ + public void exec() { + stream.forEach(t -> {}); + } + +} 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 5e1fcefb3..9b9cf5c18 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 @@ -15,7 +15,9 @@ import java.util.stream.Collectors; /** * 可变的汇聚操作{@link Collector} 相关工具封装 * - * @author looly, VampireAchao + * @author looly + * @author VampireAchao + * @author huangchengxing * @since 5.6.7 */ public class CollectorUtil { @@ -243,6 +245,20 @@ public class CollectorUtil { ); } + /** + * 将流转为{@link EntryStream} + * + * @param keyMapper 键的映射方法 + * @param 输入元素类型 + * @param 元素的键类型 + * @return 收集器 + * @since 6.0.0 + */ + public static Collector, EntryStream> toEntryStream( + Function keyMapper) { + return toEntryStream(keyMapper, Function.identity()); + } + /** * 将流转为{@link EntryStream} * @@ -252,6 +268,7 @@ public class CollectorUtil { * @param 元素的键类型 * @param 元素的值类型 * @return 收集器 + * @since 6.0.0 */ public static Collector, EntryStream> toEntryStream( Function keyMapper, Function valueMapper) { @@ -265,6 +282,7 @@ public class CollectorUtil { * * @param 输入元素类型 * @return 收集器 + * @since 6.0.0 */ public static Collector> toEasyStream() { return transform(ArrayList::new, EasyStream::of); @@ -275,7 +293,7 @@ public class CollectorUtil { * 返回的收集器的效果等同于: *
{@code
 	 * 	Collection coll = Stream.of(a, b, c, d)
-	 * 		.collect(Collectors.toCollection(collFactory));
+	 * 		.collect(Collectors.toColl(collFactory));
 	 * 	R result = mapper.apply(coll);
 	 * }
* @@ -285,6 +303,7 @@ public class CollectorUtil { * @param 输入元素类型 * @param 中间收集输入元素的集合类型 * @return 收集器 + * @since 6.0.0 */ public static > Collector transform( Supplier collFactory, Function mapper) { @@ -308,6 +327,7 @@ public class CollectorUtil { * @param 返回值类型 * @param 输入元素类型 * @return 收集器 + * @since 6.0.0 */ public static Collector, R> transform(Function, R> mapper) { return transform(ArrayList::new, mapper); 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 6fed9712c..1434cf099 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 @@ -1,21 +1,20 @@ package cn.hutool.core.stream; import cn.hutool.core.lang.Opt; -import cn.hutool.core.lang.mutable.MutableInt; import cn.hutool.core.lang.mutable.MutableObj; -import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjUtil; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Spliterator; import java.util.function.*; import java.util.stream.Stream; import java.util.stream.StreamSupport; /** - *

{@link Stream}的扩展实现,基于原生Stream进行了封装和增强。
+ *

单元素的扩展流实现。基于原生Stream进行了封装和增强。
* 作者经对比了vavr、eclipse-collection、stream-ex以及其他语言的api,结合日常使用习惯,进行封装和拓展 * Stream为集合提供了一些易用api,它让开发人员能使用声明式编程的方式去编写代码。 * @@ -46,10 +45,11 @@ import java.util.stream.StreamSupport; * * @author VampireAchao * @author emptypoint + * @author huangchengxing * @see java.util.stream.Stream * @since 6.0.0 */ -public class EasyStream extends AbstractEnhancedStreamWrapper> { +public class EasyStream extends AbstractEnhancedWrappedStream> { /** * 构造 @@ -69,10 +69,10 @@ public class EasyStream extends AbstractEnhancedStreamWrapper 元素的类型 * @return a stream builder */ - public static FastStreamBuilder builder() { - return new FastStreamBuilder() { + public static Builder builder() { + return new Builder() { private static final long serialVersionUID = 1L; - private final Builder streamBuilder = Stream.builder(); + private final Stream.Builder streamBuilder = Stream.builder(); @Override public void accept(final T t) { @@ -241,20 +241,6 @@ public class EasyStream extends AbstractEnhancedStreamWrapper 返回类型 - * @param mapper 操作 - * @param value 用来匹配的值 - * @return 与 指定操作结果 匹配 指定值 的元素组成的流 - */ - public EasyStream filter(final Function mapper, final R value) { - Objects.requireNonNull(mapper); - return filter(e -> Objects.equals(mapper.apply(e), value)); - } - /** * 返回与指定函数将元素作为参数执行的结果组成的流 * 这是一个无状态中间操作 @@ -265,185 +251,10 @@ public class EasyStream extends AbstractEnhancedStreamWrapper EasyStream map(final Function mapper) { + Objects.requireNonNull(mapper); return new EasyStream<>(stream.map(mapper)); } - /** - * 返回 元素 转换后 并且不为 {@code null} 的 新元素组成的流
- * 这是一个无状态中间操作
- *

{@code
-	 * // 等价于先调用map再调用nonNull
-	 * .map(...).nonNull()...
-	 * }
- * - * @param mapper 指定的函数 - * @param 函数执行后返回的类型 - * @return 新元素组成的流 - */ - public EasyStream mapNonNull(final Function mapper) { - return nonNull().map(mapper).nonNull(); - } - - /** - * 返回与指定函数将元素作为参数执行的结果组成的流,操作带下标,并行流时下标永远为-1 - * 这是一个无状态中间操作 - * - * @param mapper 指定的函数 - * @param 函数执行后返回的类型 - * @return 返回叠加操作后的流 - */ - public EasyStream mapIdx(final BiFunction mapper) { - Objects.requireNonNull(mapper); - if (isParallel()) { - return map(e -> mapper.apply(e, NOT_FOUND_INDEX)); - } else { - final MutableInt index = new MutableInt(NOT_FOUND_INDEX); - return map(e -> mapper.apply(e, index.incrementAndGet())); - } - } - - /** - * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作,返回多个流所有元素组成的流
- * 这是一个无状态中间操作
- * 例如,将users里所有user的id和parentId组合在一起,形成一个新的流: - *
{@code
-	 *     FastStream ids = FastStream.of(users).flatMap(user -> FastStream.of(user.getId(), user.getParentId()));
-	 * }
- * - * @param mapper 操作,返回流 - * @param 拆分后流的元素类型 - * @return 返回叠加拆分操作后的流 - */ - @Override - public EasyStream flatMap(final Function> mapper) { - return new EasyStream<>(stream.flatMap(mapper)); - } - - /** - * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作,返回多个流所有元素组成的流,操作带下标,并行流时下标永远为-1 - * 这是一个无状态中间操作 - * - * @param mapper 操作,返回流 - * @param 拆分后流的元素类型 - * @return 返回叠加拆分操作后的流 - */ - public EasyStream flatMapIdx(final BiFunction> mapper) { - Objects.requireNonNull(mapper); - if (isParallel()) { - return flatMap(e -> mapper.apply(e, NOT_FOUND_INDEX)); - } else { - final MutableInt index = new MutableInt(NOT_FOUND_INDEX); - return flatMap(e -> mapper.apply(e, index.incrementAndGet())); - } - } - - /** - * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作, 转换为迭代器元素, - * 最后返回所有迭代器的所有元素组成的流
- * 这是一个无状态中间操作
- * 例如,将users里所有user的id和parentId组合在一起,形成一个新的流: - *
{@code
-	 *     FastStream ids = FastStream.of(users).flat(user -> FastStream.of(user.getId(), user.getParentId()));
-	 * }
- * - * @param mapper 操作,返回可迭代对象 - * @param 拆分后流的元素类型 - * @return 返回叠加拆分操作后的流 - */ - public EasyStream flat(final Function> mapper) { - Objects.requireNonNull(mapper); - return flatMap(w -> of(mapper.apply(w))); - } - - /** - * 扩散流操作,可能影响流元素个数,对过滤后的非{@code null}元素执行mapper操作,转换为迭代器, - * 并过滤迭代器中为{@code null}的元素, 返回所有迭代器的所有非空元素组成的流
- * 这是一个无状态中间操作
- * - * @param mapper 操作,返回流 - * @param 拆分后流的元素类型 - * @return 返回叠加拆分操作后的流 - * @see #flat(Function) - * @see #nonNull() - */ - public EasyStream flatNonNull(final Function> mapper) { - return nonNull().flat(mapper).nonNull(); - } - - /** - * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作,返回多个流所有元素组成的流,操作带一个方法,调用该方法可增加元素 - * 这是一个无状态中间操作 - * - * @param mapper 操作,返回流 - * @param 拆分后流的元素类型 - * @return 返回叠加拆分操作后的流 - */ - public EasyStream mapMulti(final BiConsumer> mapper) { - Objects.requireNonNull(mapper); - return flatMap(e -> { - final FastStreamBuilder buffer = EasyStream.builder(); - mapper.accept(e, buffer); - return buffer.build(); - }); - } - - /** - * 返回一个具有去重特征的流 非并行流(顺序流)下对于重复元素,保留遇到顺序中最先出现的元素,并行流情况下不能保证具体保留哪一个 - * 这是一个有状态中间操作 - * - * @param 参数类型 - * @param keyExtractor 去重依据 - * @return 一个具有去重特征的流 - */ - public EasyStream distinct(final Function keyExtractor) { - Objects.requireNonNull(keyExtractor); - if (isParallel()) { - final ConcurrentHashMap exists = MapUtil.newConcurrentHashMap(); - // 标记是否出现过null值,用于保留第一个出现的null - // 由于ConcurrentHashMap的key不能为null,所以用此变量来标记 - final AtomicBoolean hasNull = new AtomicBoolean(false); - return of(stream.filter(e -> { - final F key = keyExtractor.apply(e); - if (key == null) { - // 已经出现过null值,跳过该值 - if (hasNull.get()) { - return false; - } - hasNull.set(Boolean.TRUE); - return true; - } else { - // 第一次出现的key返回true - return null == exists.putIfAbsent(key, Boolean.TRUE); - } - })).parallel(); - } else { - final Set exists = new HashSet<>(); - return of(stream.filter(e -> exists.add(keyExtractor.apply(e)))); - } - } - - /** - * 与给定元素组成的流合并,成为新的流 - * - * @param obj 元素 - * @return 流 - */ - public EasyStream push(final T obj) { - return EasyStream.concat(this.stream, of(obj)); - } - - - /** - * 给定元素组成的流与当前流合并,成为新的流 - * - * @param obj 元素 - * @return 流 - */ - public EasyStream unshift(final T obj) { - return EasyStream.concat(of(obj), this.stream); - } - - /** * 根据一个原始的流,返回一个新包装类实例 * @@ -456,16 +267,19 @@ public class EasyStream extends AbstractEnhancedStreamWrapper将集合转换为树,默认用 {@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 此处是id、parentId的泛型限制 - * @return list 组装好的树 + * @return list 组装好的树
* eg: - * {@code List studentTree = EasyStream.of(students).toTree(Student::getId, Student::getParentId, Student::setChildren) } + *
{@code
+	 * List studentTree = EasyStream.of(students).
+	 * 	toTree(Student::getId, Student::getParentId, Student::setChildren);
+	 * }
*/ public > List toTree(Function idGetter, Function pIdGetter, @@ -483,15 +297,19 @@ public class EasyStream extends AbstractEnhancedStreamWrapper Objects.equals(s.getParentId(),0L) } * @param 此处是id、parentId的泛型限制 - * @return list 组装好的树 + * @return list 组装好的树
* eg: - * {@code List studentTree = EasyStream.of(students).toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent) } + *
{@code
+	 * List studentTree = EasyStream.of(students).
+	 * 	.toTree(Student::getId, Student::getParentId, Student::setChildren, Student::getMatchParent);
+	 * }
*/ public > List toTree(Function idGetter, Function pIdGetter, BiConsumer> childrenSetter, Predicate parentPredicate) { + Objects.requireNonNull(parentPredicate); List list = toList(); List parents = EasyStream.of(list).filter(e -> // 此处是为了适配 parentPredicate.test空指针 情况 @@ -516,6 +334,9 @@ public class EasyStream extends AbstractEnhancedStreamWrapper> childrenSetter, Map> pIdValuesMap, List parents) { + Objects.requireNonNull(idGetter); + Objects.requireNonNull(childrenSetter); + Objects.requireNonNull(pIdValuesMap); MutableObj>> recursiveRef = new MutableObj<>(); Consumer> recursive = values -> EasyStream.of(values, isParallel()).forEach(value -> { List children = pIdValuesMap.get(idGetter.apply(value)); @@ -528,89 +349,11 @@ public class EasyStream extends AbstractEnhancedStreamWrapper flatTree(Function> childrenGetter, BiConsumer> childrenSetter) { - MutableObj>> recursiveRef = new MutableObj<>(); - Function> recursive = e -> EasyStream.of(childrenGetter.apply(e)).flat(recursiveRef.get()).unshift(e); - recursiveRef.set(recursive); - return flat(recursive).peek(e -> childrenSetter.accept(e, null)); - } - - /** - * 将 现有元素 与 给定迭代器中对应位置的元素 使用 zipper 转换为新的元素,并返回新元素组成的流
- * 新流的数量为两个集合中较小的数量, 即, 只合并下标位置相同的部分
- * - * @param other 给定的迭代器 - * @param zipper 两个元素的合并器 - * @param 给定的迭代对象类型 - * @param 合并后的结果对象类型 - * @return 合并后的结果对象的流 - */ - public EasyStream zip(final Iterable other, - final BiFunction zipper) { - Objects.requireNonNull(zipper); - final Spliterator keys = spliterator(); - final Spliterator values = Opt.ofNullable(other).map(Iterable::spliterator).orElseGet(Spliterators::emptySpliterator); - // 获取两个Spliterator的中较小的数量 - // 如果Spliterator经过流操作, getExactSizeIfKnown()可能会返回-1, 所以默认大小为 ArrayList.DEFAULT_CAPACITY - final int sizeIfKnown = (int) Math.max(Math.min(keys.getExactSizeIfKnown(), values.getExactSizeIfKnown()), 10); - final List list = new ArrayList<>(sizeIfKnown); - // 保存第一个Spliterator的值 - final MutableObj key = new MutableObj<>(); - // 保存第二个Spliterator的值 - final MutableObj value = new MutableObj<>(); - // 当两个Spliterator中都还有剩余元素时 - while (keys.tryAdvance(key::set) && values.tryAdvance(value::set)) { - list.add(zipper.apply(key.get(), value.get())); - } - return of(list).parallel(isParallel()).onClose(stream::close); - } - - /** - * 按指定长度切分为双层流 - *

- * 形如:[1,2,3,4,5] -> [[1,2], [3,4], [5,6]] - *

- * - * @param batchSize 指定长度, 正整数 - * @return 切好的流 - */ - public EasyStream> split(final int batchSize) { - final List list = toList(); - final int size = list.size(); - // 指定长度 大于等于 列表长度 - if (size <= batchSize) { - // 返回第一层只有单个元素的双层流,形如:[[1,2,3,4,5]] - return EasyStream.>of(of(list, isParallel())); - } - return iterate(0, i -> i < size, i -> i + batchSize) - .map(skip -> of(list.subList(skip, Math.min(size, skip + batchSize)), isParallel())) - .parallel(isParallel()) - .onClose(stream::close); - } - - /** - * 按指定长度切分为元素为list的流 - *

- * 形如:[1,2,3,4,5] -> [[1,2], [3,4], [5,6]] - *

- * - * @param batchSize 指定长度, 正整数 - * @return 切好的流 - */ - public EasyStream> splitList(final int batchSize) { - return split(batchSize).map(EasyStream::toList); - } - - public interface FastStreamBuilder extends Consumer, cn.hutool.core.builder.Builder> { + public interface Builder extends Consumer, cn.hutool.core.builder.Builder> { /** * Adds an element to the stream being built. @@ -625,7 +368,7 @@ public class EasyStream extends AbstractEnhancedStreamWrapper */ - default FastStreamBuilder add(final T t) { + default Builder add(final T t) { accept(t); return this; } 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 index c9eadd81c..cb030f9df 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/EntryStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/EntryStream.java @@ -14,15 +14,16 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; /** - *

参考StreamEx的EntryStream与vavr的Map,是针对键值对对象{@link Map.Entry}特化的增强流实现。
+ *

参考StreamEx的EntryStream与vavr的Map,是针对键值对对象{@link Map.Entry}特化的单元素增强流实现。
* 本身可视为一个元素类型为{@link Map.Entry}的{@link Stream}, * 用于支持流式处理{@link Map}集合中的、或其他键值对类型的数据。 * * @param 键类型 * @param 值类型 * @author huangchengxing + * @since 6.0.0 */ -public class EntryStream extends AbstractEnhancedStreamWrapper, EntryStream> { +public class EntryStream extends AbstractEnhancedWrappedStream, EntryStream> { /** * 默认的空键值对 @@ -222,8 +223,7 @@ public class EntryStream extends AbstractEnhancedStreamWrapper nonNull() { + public EntryStream nonNullKeyValue() { return super.filter(e -> ObjUtil.isNotNull(e) && ObjUtil.isNotNull(e.getKey()) && ObjUtil.isNotNull(e.getValue())); } @@ -232,7 +232,7 @@ public class EntryStream extends AbstractEnhancedStreamWrapper keyNonNull() { + public EntryStream nonNullKey() { return super.filter(e -> ObjUtil.isNotNull(e) && ObjUtil.isNotNull(e.getKey())); } @@ -241,7 +241,7 @@ public class EntryStream extends AbstractEnhancedStreamWrapper valueNonNull() { + public EntryStream nonNullValue() { return super.filter(e -> ObjUtil.isNotNull(e) && ObjUtil.isNotNull(e.getValue())); } @@ -298,7 +298,7 @@ public class EntryStream extends AbstractEnhancedStreamWrapper append(K key, V value) { + public EntryStream push(K key, V value) { return wrapping(Stream.concat(stream, Stream.of(ofEntry(key, value)))); } @@ -309,7 +309,7 @@ public class EntryStream extends AbstractEnhancedStreamWrapper prepend(K key, V value) { + public EntryStream unshift(K key, V value) { return wrapping(Stream.concat(Stream.of(ofEntry(key, value)), stream)); } @@ -319,6 +319,7 @@ public class EntryStream extends AbstractEnhancedStreamWrapper append(Iterable> entries) { if (IterUtil.isEmpty(entries)) { return this; @@ -334,6 +335,7 @@ public class EntryStream extends AbstractEnhancedStreamWrapper prepend(Iterable> entries) { if (IterUtil.isEmpty(entries)) { return this; @@ -724,7 +726,7 @@ public class EntryStream extends AbstractEnhancedStreamWrapper Map.Entry ofEntry(Map.Entry entry) { + static Map.Entry ofEntry(Map.Entry entry) { return ObjUtil.defaultIfNull( entry, e -> ofEntry(e.getKey(), e.getValue()), (Map.Entry)EMPTY_ENTRY ); @@ -733,7 +735,7 @@ public class EntryStream extends AbstractEnhancedStreamWrapper Map.Entry ofEntry(K key, V value) { + static Map.Entry ofEntry(K key, V value) { return new AbstractMap.SimpleImmutableEntry<>(key, value); } diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/SimpleStreamWrapper.java b/hutool-core/src/main/java/cn/hutool/core/stream/SimpleStreamWrapper.java deleted file mode 100644 index 636e54f1f..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/stream/SimpleStreamWrapper.java +++ /dev/null @@ -1,55 +0,0 @@ -package cn.hutool.core.stream; - -import java.util.function.Function; -import java.util.stream.Stream; - -/** - * {@link AbstractEnhancedStreamWrapper}的基本实现 - * - * @author huangchengxing - */ -public class SimpleStreamWrapper extends AbstractEnhancedStreamWrapper> { - - /** - * 创建一个流包装器 - * - * @param stream 包装的流对象 - * @throws NullPointerException 当{@code stream}为{@code null}时抛出 - */ - SimpleStreamWrapper(Stream stream) { - super(stream); - } - - /** - * 将一个流包装为{@link SimpleStreamWrapper} - * - * @param source 被包装的流 - * @return 包装后的流 - */ - @Override - public SimpleStreamWrapper wrapping(Stream source) { - return new SimpleStreamWrapper<>(source); - } - - /** - * 将当前流中元素映射为另一类型 - * - * @param mapper 映射方法 - * @return 映射后的流 - */ - @Override - public SimpleStreamWrapper map(Function mapper) { - return new SimpleStreamWrapper<>(stream().map(mapper)); - } - - /** - * 将当前流中元素展开为流,然后返回由这些新流中元素组成的流 - * - * @param mapper 映射方法 - * @return 映射后的流 - */ - @Override - public SimpleStreamWrapper flatMap(Function> mapper) { - return new SimpleStreamWrapper<>(stream().flatMap(mapper)); - } -} diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/TerminableStreamWrapper.java b/hutool-core/src/main/java/cn/hutool/core/stream/TerminableWrappedStream.java similarity index 59% rename from hutool-core/src/main/java/cn/hutool/core/stream/TerminableStreamWrapper.java rename to hutool-core/src/main/java/cn/hutool/core/stream/TerminableWrappedStream.java index eee30f86f..0e71feb09 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/TerminableStreamWrapper.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/TerminableWrappedStream.java @@ -9,70 +9,26 @@ import java.util.*; import java.util.function.*; import java.util.stream.Collector; import java.util.stream.Collectors; +import java.util.stream.Stream; /** - * {@link StreamWrapper}的扩展,为实现类提供更多终端操作方法 + * {@link WrappedStream}的扩展,用于为实现类提供更多终端操作方法的增强接口, + * 该接口提供的方法,返回值类型都不为{@link Stream}。 * * @param 流中的元素类型 - * @param 链式调用获得的实现类类型 + * @param {@link TerminableWrappedStream}的实现类类型 * @author huangchengxing + * @since 6.0.0 */ -public interface TerminableStreamWrapper> extends StreamWrapper { - - // region ============ join ============ - - /** - * 返回拼接后的字符串 - * - * @return 拼接后的字符串 - */ - default String join() { - return this.join(""); - } - - /** - * 返回拼接后的字符串 - * - * @param delimiter 分隔符 - * @return 拼接后的字符串 - */ - default String join(CharSequence delimiter) { - return this.join(delimiter, "", ""); - } - - /** - * 返回拼接后的字符串 - * - * @param delimiter 分隔符 - * @param prefix 前缀 - * @param suffix 后缀 - * @return 拼接后的字符串 - */ - default String join(CharSequence delimiter, - CharSequence prefix, - CharSequence suffix) { - return stream().map(String::valueOf).collect(Collectors.joining(delimiter, prefix, suffix)); - } - - // endregion +public interface TerminableWrappedStream> extends WrappedStream { // region ============ to collection ============ - /** - * 转换成集合 - * - * @param collectionFactory 集合工厂(可以是集合构造器) - * @param 集合类型 - * @return 集合 - */ - default > C toColl(Supplier collectionFactory) { - return stream().collect(Collectors.toCollection(collectionFactory)); - } - /** * 转换为{@link ArrayList} * * @return 集合 + * @see #toColl(Supplier) */ default List toList() { return this.toColl(ArrayList::new); @@ -82,7 +38,8 @@ public interface TerminableStreamWrapper toUnmodifiableList() { return Collections.unmodifiableList(this.toList()); } @@ -91,7 +48,8 @@ public interface TerminableStreamWrapper toSet() { return this.toColl(HashSet::new); } @@ -100,11 +58,24 @@ public interface TerminableStreamWrapper toUnmodifiableSet() { return Collections.unmodifiableSet(this.toSet()); } + /** + * 转换成集合 + * + * @param collectionFactory 集合工厂(可以是集合构造器) + * @param 集合类型 + * @return 集合 + */ + default > C toColl(Supplier collectionFactory) { + Objects.requireNonNull(collectionFactory); + return stream().collect(Collectors.toCollection(collectionFactory)); + } + // endregion // region ============ to map ============ @@ -115,6 +86,7 @@ public interface TerminableStreamWrapper key类型 * @return map + * @see #toMap(Function, Function, BinaryOperator, Supplier) */ default Map toMap(Function keyMapper) { return this.toMap(keyMapper, Function.identity()); @@ -128,6 +100,7 @@ public interface TerminableStreamWrapper key类型 * @param value类型 * @return map + * @see #toMap(Function, Function, BinaryOperator, Supplier) */ default Map toMap( Function keyMapper, Function valueMapper) { @@ -142,6 +115,7 @@ public interface TerminableStreamWrapper key类型 * @param value类型 * @return map + * @see #toMap(Function, Function, BinaryOperator, Supplier) */ default Map toUnmodifiableMap( Function keyMapper, Function valueMapper) { @@ -157,6 +131,7 @@ public interface TerminableStreamWrapper key类型 * @param value类型 * @return map + * @see #toMap(Function, Function, BinaryOperator, Supplier) */ default Map toMap( Function keyMapper, @@ -174,6 +149,7 @@ public interface TerminableStreamWrapper key类型 * @param value类型 * @return map + * @see #toMap(Function, Function, BinaryOperator, Supplier) */ default Map toUnmodifiableMap( Function keyMapper, @@ -201,6 +177,10 @@ public interface TerminableStreamWrapper valueMapper, BinaryOperator mergeFunction, Supplier mapSupplier) { + Objects.requireNonNull(keyMapper); + Objects.requireNonNull(valueMapper); + Objects.requireNonNull(mergeFunction); + Objects.requireNonNull(mapSupplier); return stream().collect(Collectors.toMap(keyMapper, valueMapper, mergeFunction, mapSupplier)); } @@ -219,6 +199,7 @@ public interface TerminableStreamWrapper */ default Map toZip(Iterable other) { + Objects.requireNonNull(other); // value对象迭代器 final Iterator iterator = Opt.ofNullable(other).map(Iterable::iterator).orElseGet(Collections::emptyIterator); if (this.isParallel()) { @@ -235,129 +216,20 @@ public interface TerminableStreamWrapper 实体中的分组依据对应类型,也是Map中key的类型 - * @return map - */ - default Map> group(Function classifier) { - return this.group(classifier, Collectors.toList()); - } - - /** - * 通过给定分组依据进行分组 - * - * @param classifier 分组依据,得到的键为{@code null}时不会抛出异常 - * @param downstream 下游操作 - * @param 实体中的分组依据对应类型,也是Map中key的类型 - * @param 下游操作对应返回类型,也是Map中value的类型 - * @param 下游操作在进行中间操作时对应类型 - * @return map - */ - default Map group( - Function classifier, Collector downstream) { - return this.group(classifier, HashMap::new, downstream); - } - - /** - * 通过给定分组依据进行分组 - * - * @param classifier 分组依据,得到的键为{@code null}时不会抛出异常 - * @param mapFactory 提供的map - * @param downstream 下游操作 - * @param 实体中的分组依据对应类型,也是Map中key的类型 - * @param 下游操作对应返回类型,也是Map中value的类型 - * @param 下游操作在进行中间操作时对应类型 - * @param 最后返回结果Map类型 - * @return map - * @see CollectorUtil#groupingBy(Function, Supplier, Collector) - */ - default > M group( - Function classifier, - Supplier mapFactory, - Collector downstream) { - return stream().collect(CollectorUtil.groupingBy(classifier, mapFactory, downstream)); - } - - /** - * 根据给定判断条件分组 - * - * @param predicate 判断条件 - * @return map - */ - default Map> partition(Predicate predicate) { - return this.partition(predicate, ArrayList::new); - } - - /** - * 根据给定判断条件分组 - * - * @param predicate 判断条件 - * @param collFactory 提供的集合 - * @return map - */ - default > Map partition(Predicate predicate, Supplier collFactory) { - return this.partition(predicate, Collectors.toCollection(collFactory)); - } - - /** - * 根据给定判断条件分组 - * - * @param predicate 判断条件 - * @param downstream 下游操作 - * @param 返回值类型 - * @return map - */ - default Map partition(Predicate predicate, Collector downstream) { - return stream().collect(Collectors.partitioningBy(predicate, downstream)); - } - - // endregion - - // region ============ foreach ============ + // region ============ to optional ============ /** - * 对流里面的每一个元素执行一个操作,操作带下标,并行流时下标永远为-1 - * 这是一个终端操作 + * 将当前流转为另一对象。用于提供针对流本身而非流中元素的操作 * - * @param action 操作 + * @param 转换类型 + * @param transform 转换 + * @return 转换后的流 */ - default void forEachIdx(final BiConsumer action) { - Objects.requireNonNull(action); - if (isParallel()) { - stream().forEach(e -> action.accept(e, NOT_FOUND_INDEX)); - } else { - final MutableInt index = new MutableInt(NOT_FOUND_INDEX); - stream().forEach(e -> action.accept(e, index.incrementAndGet())); - } + default Optional transform(final Function transform) { + Objects.requireNonNull(transform); + return Optional.ofNullable(transform.apply(wrapping(this))); } - /** - * 对流里面的每一个元素按照顺序执行一个操作,操作带下标,并行流时下标永远为-1 - * 这是一个终端操作 - * - * @param action 操作 - */ - default void forEachOrderedIdx(final BiConsumer action) { - Objects.requireNonNull(action); - if (isParallel()) { - stream().forEachOrdered(e -> action.accept(e, NOT_FOUND_INDEX)); - } else { - final MutableInt index = new MutableInt(NOT_FOUND_INDEX); - stream().forEachOrdered(e -> action.accept(e, index.incrementAndGet())); - } - } - - // endregion - - // region ============ find & get ============ - - - /** * 获取与给定断言匹配的第一个元素 * @@ -365,6 +237,7 @@ public interface TerminableStreamWrapper findFirst(final Predicate predicate) { + Objects.requireNonNull(predicate); return stream().filter(predicate).findFirst(); } @@ -377,9 +250,9 @@ public interface TerminableStreamWrapper predicate) { Objects.requireNonNull(predicate); if (isParallel()) { - return NOT_FOUND_INDEX; + return NOT_FOUND_ELEMENT_INDEX; } else { - final MutableInt index = new MutableInt(NOT_FOUND_INDEX); + final MutableInt index = new MutableInt(NOT_FOUND_ELEMENT_INDEX); stream().filter(e -> { index.increment(); return predicate.test(e); @@ -425,9 +298,9 @@ public interface TerminableStreamWrapper predicate) { Objects.requireNonNull(predicate); if (isParallel()) { - return NOT_FOUND_INDEX; + return NOT_FOUND_ELEMENT_INDEX; } else { - final MutableInt idxRef = new MutableInt(NOT_FOUND_INDEX); + final MutableInt idxRef = new MutableInt(NOT_FOUND_ELEMENT_INDEX); forEachIdx((e, i) -> { if (predicate.test(e)) { idxRef.set(i); @@ -450,7 +323,7 @@ public interface TerminableStreamWrapper 实体中的分组依据对应类型,也是Map中key的类型 + * @return map + * @see #group(Function, Supplier, Collector) + */ + default Map> group(Function classifier) { + return this.group(classifier, Collectors.toList()); + } + + /** + * 通过给定分组依据进行分组 + * + * @param classifier 分组依据,得到的键为{@code null}时不会抛出异常 + * @param downstream 下游操作 + * @param 实体中的分组依据对应类型,也是Map中key的类型 + * @param 下游操作对应返回类型,也是Map中value的类型 + * @param 下游操作在进行中间操作时对应类型 + * @return map + * @see #group(Function, Supplier, Collector) + */ + default Map group( + Function classifier, Collector downstream) { + return this.group(classifier, HashMap::new, downstream); + } + + /** + * 通过给定分组依据进行分组 + * + * @param classifier 分组依据,得到的键为{@code null}时不会抛出异常 + * @param mapFactory 提供的map + * @param downstream 下游操作 + * @param 实体中的分组依据对应类型,也是Map中key的类型 + * @param 下游操作对应返回类型,也是Map中value的类型 + * @param 下游操作在进行中间操作时对应类型 + * @param 最后返回结果Map类型 + * @return map + * @see CollectorUtil#groupingBy(Function, Supplier, Collector) + */ + default > M group( + Function classifier, + Supplier mapFactory, + Collector downstream) { + Objects.requireNonNull(classifier); + Objects.requireNonNull(mapFactory); + Objects.requireNonNull(downstream); + return stream().collect(CollectorUtil.groupingBy(classifier, mapFactory, downstream)); + } + + /** + * 根据给定判断条件分组 + * + * @param predicate 判断条件 + * @return map + * @see #partitioning(Predicate, Collector) + */ + default Map> partitioning(Predicate predicate) { + return this.partitioning(predicate, ArrayList::new); + } + + /** + * 根据给定判断条件分组 + * + * @param predicate 判断条件 + * @param collFactory 提供的集合 + * @return map + * @see #partitioning(Predicate, Collector) + */ + default > Map partitioning(Predicate predicate, Supplier collFactory) { + return this.partitioning(predicate, Collectors.toCollection(collFactory)); + } + + /** + * 根据给定判断条件分组 + * + * @param predicate 判断条件 + * @param downstream 下游操作 + * @param 返回值类型 + * @return map + */ + default Map partitioning(Predicate predicate, Collector downstream) { + Objects.requireNonNull(predicate); + Objects.requireNonNull(downstream); + return stream().collect(Collectors.partitioningBy(predicate, downstream)); + } + + // endregion + + // region ============ foreach ============ + + /** + * 对流里面的每一个元素执行一个操作,操作带下标,并行流时下标永远为-1 + * 这是一个终端操作 + * + * @param action 操作 + */ + default void forEachIdx(final BiConsumer action) { + Objects.requireNonNull(action); + if (isParallel()) { + stream().forEach(e -> action.accept(e, NOT_FOUND_ELEMENT_INDEX)); + } else { + final MutableInt index = new MutableInt(NOT_FOUND_ELEMENT_INDEX); + stream().forEach(e -> action.accept(e, index.incrementAndGet())); + } + } + + /** + * 对流里面的每一个元素按照顺序执行一个操作,操作带下标,并行流时下标永远为-1 + * 这是一个终端操作 + * + * @param action 操作 + */ + default void forEachOrderedIdx(final BiConsumer action) { + Objects.requireNonNull(action); + if (isParallel()) { + stream().forEachOrdered(e -> action.accept(e, NOT_FOUND_ELEMENT_INDEX)); + } else { + final MutableInt index = new MutableInt(NOT_FOUND_ELEMENT_INDEX); + stream().forEachOrdered(e -> action.accept(e, index.incrementAndGet())); + } + } + + // endregion + } diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/TransformableStreamWrapper.java b/hutool-core/src/main/java/cn/hutool/core/stream/TransformableStreamWrapper.java deleted file mode 100644 index 10ad264bf..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/stream/TransformableStreamWrapper.java +++ /dev/null @@ -1,216 +0,0 @@ -package cn.hutool.core.stream; - -import cn.hutool.core.collection.ListUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.lang.Console; -import cn.hutool.core.lang.mutable.MutableInt; -import cn.hutool.core.util.ArrayUtil; - -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiConsumer; -import java.util.function.BiPredicate; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * {@link StreamWrapper}的扩展,为实现类提供更多中间操作方法 - * - * @param 流中的元素类型 - * @param 链式调用获得的实现类类型 - * @author huangchengxing - */ -public interface TransformableStreamWrapper> extends StreamWrapper { - - /** - * 过滤元素,返回与指定断言匹配的元素组成的流,断言带下标,并行流时下标永远为-1 - * 这是一个无状态中间操作 - * - * @param predicate 断言 - * @return 返回叠加过滤操作后的流 - */ - default I filterIdx(final BiPredicate predicate) { - Objects.requireNonNull(predicate); - if (isParallel()) { - return filter(e -> predicate.test(e, NOT_FOUND_INDEX)); - } else { - final MutableInt index = new MutableInt(NOT_FOUND_INDEX); - return filter(e -> predicate.test(e, index.incrementAndGet())); - } - } - - /** - * 过滤掉空元素 - * - * @return 过滤后的流 - */ - default I nonNull() { - return filter(Objects::nonNull); - } - - /** - * 返回与指定函数将元素作为参数执行后组成的流。操作带下标,并行流时下标永远为-1 - * 这是一个无状态中间操作 - * @param action 指定的函数 - * @return 返回叠加操作后的FastStream - * @apiNote 该方法存在的意义主要是用来调试 - * 当你需要查看经过操作管道某处的元素和下标,可以执行以下操作: - *

{@code
-	 *     Stream.of("one", "two", "three", "four")
-	 * 				.filter(e -> e.length() > 3)
-	 * 				.peekIdx((e,i) -> System.out.println("Filtered value: " + e + " Filtered idx:" + i))
-	 * 				.map(String::toUpperCase)
-	 * 				.peekIdx((e,i) -> System.out.println("Mapped value: " + e + " Mapped idx:" + i))
-	 * 				.collect(Collectors.toList());
-	 * }
- */ - default I peekIdx(BiConsumer action) { - Objects.requireNonNull(action); - if (isParallel()) { - return peek(e -> action.accept(e, NOT_FOUND_INDEX)); - } else { - AtomicInteger index = new AtomicInteger(NOT_FOUND_INDEX); - return peek(e -> action.accept(e, index.incrementAndGet())); - } - } - - /** - * 返回叠加调用{@link Console#log(Object)}打印出结果的流 - * - * @return 返回叠加操作后的FastStream - */ - default I log() { - return peek(Console::log); - } - - /** - * 反转顺序 - * - * @return 反转元素顺序 - */ - @SuppressWarnings("unchecked") - default I reverse() { - final T[] array = (T[]) toArray(); - ArrayUtil.reverse(array); - return wrapping(Stream.of(array)).parallel(isParallel()); - } - - /** - * 更改流的并行状态 - * - * @param parallel 是否并行 - * @return 流 - */ - default I parallel(final boolean parallel) { - return parallel ? parallel() : sequential(); - } - - /** - * 与给定元素组成的流合并,成为新的流 - * - * @param obj 元素 - * @return 流 - */ - @SuppressWarnings("unchecked") - default I push(final T... obj) { - Stream result = stream(); - if (ArrayUtil.isNotEmpty(obj)) { - result = Stream.concat(stream(), Stream.of(obj)); - } - return wrapping(result); - } - - /** - * 给定元素组成的流与当前流合并,成为新的流 - * - * @param obj 元素 - * @return 流 - */ - default I unshift(final T... obj) { - Stream result = stream(); - if (ArrayUtil.isNotEmpty(obj)) { - result = Stream.concat(Stream.of(obj), stream()); - } - return wrapping(result); - } - - /** - * 通过删除或替换现有元素或者原地添加新的元素来修改列表,并以列表形式返回被修改的内容。此方法不会改变原列表。 - * 类似js的splice函数 - * - * @param start 起始下标 - * @param deleteCount 删除个数,正整数 - * @param items 放入值 - * @return 操作后的流 - */ - default I splice(final int start, final int deleteCount, final T... items) { - final List elements = stream().collect(Collectors.toList()); - return wrapping(ListUtil.splice(elements, start, deleteCount, items).stream()) - .parallel(isParallel()); - } - - /** - * 保留 与指定断言 匹配时的元素, 在第一次不匹配时终止, 抛弃当前(第一个不匹配元素)及后续所有元素 - *

与 jdk9 中的 takeWhile 方法不太一样, 这里的实现是个 顺序的、有状态的中间操作

- *
本环节中是顺序执行的, 但是后续操作可以支持并行流: {@code
-	 * FastStream.iterate(1, i -> i + 1)
-	 * 	.parallel()
-	 * 	// 顺序执行
-	 * 	.takeWhile(e -> e < 50)
-	 * 	// 并发
-	 * 	.map(e -> e + 1)
-	 * 	// 并发
-	 * 	.map(String::valueOf)
-	 * 	.toList();
-	 * }
- *

但是不建议在并行流中使用, 除非你确定 takeWhile 之后的操作能在并行流中受益很多

- * - * @param predicate 断言 - * @return 与指定断言匹配的元素组成的流 - */ - default I takeWhile(final Predicate predicate) { - Objects.requireNonNull(predicate); - return wrapping(StreamUtil.takeWhile(stream(), predicate)); - } - - /** - * 删除 与指定断言 匹配的元素, 在第一次不匹配时终止, 返回当前(第一个不匹配元素)及剩余元素组成的新流 - *

与 jdk9 中的 dropWhile 方法不太一样, 这里的实现是个 顺序的、有状态的中间操作

- *
本环节中是顺序执行的, 但是后续操作可以支持并行流: {@code
-	 * FastStream.iterate(1, i <= 100, i -> i + 1)
-	 * 	.parallel()
-	 * 	// 顺序执行
-	 * 	.dropWhile(e -> e < 50)
-	 * 	// 并发
-	 * 	.map(e -> e + 1)
-	 * 	// 并发
-	 * 	.map(String::valueOf)
-	 * 	.toList();
-	 * }
- *

但是不建议在并行流中使用, 除非你确定 dropWhile 之后的操作能在并行流中受益很多

- * - * @param predicate 断言 - * @return 剩余元素组成的流 - */ - default I dropWhile(final Predicate predicate) { - Objects.requireNonNull(predicate); - return wrapping(StreamUtil.dropWhile(stream(), predicate)); - } - - /** - * 将当前流转为另一对象。用于提供针对流本身而非流中元素的操作 - * - * @param 转换类型 - * @param transform 转换 - * @return 转换后的流 - */ - default Optional transform(final Function transform) { - Assert.notNull(transform, "transform must not null"); - return Optional.ofNullable(transform.apply(wrapping(this))); - } - -} diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/TransformableWrappedStream.java b/hutool-core/src/main/java/cn/hutool/core/stream/TransformableWrappedStream.java new file mode 100644 index 000000000..f21a1dc0e --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/stream/TransformableWrappedStream.java @@ -0,0 +1,570 @@ +package cn.hutool.core.stream; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.collection.iter.IterUtil; +import cn.hutool.core.lang.Console; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.mutable.MutableInt; +import cn.hutool.core.lang.mutable.MutableObj; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * {@link WrappedStream}的扩展,用于为实现类提供更多中间操作方法的增强接口, + * 该接口提供的方法,返回值类型都为{@link Stream}。 + * + * @param 流中的元素类型 + * @param {@link TransformableWrappedStream}的实现类类型 + * @author huangchengxing + * @since 6.0.0 + */ +public interface TransformableWrappedStream> extends WrappedStream { + + /** + * 将 现有元素 与 给定迭代器中对应位置的元素 使用 zipper 转换为新的元素,并返回新元素组成的流
+ * 新流的数量为两个集合中较小的数量, 即, 只合并下标位置相同的部分
+ * + * @param other 给定的迭代器 + * @param zipper 两个元素的合并器 + * @param 给定的迭代对象类型 + * @param 合并后的结果对象类型 + * @return 合并后的结果对象的流 + */ + default EasyStream zip(final Iterable other, + final BiFunction zipper) { + Objects.requireNonNull(zipper); + final Spliterator keys = spliterator(); + final Spliterator values = Opt.ofNullable(other).map(Iterable::spliterator).orElseGet(Spliterators::emptySpliterator); + // 获取两个Spliterator的中较小的数量 + // 如果Spliterator经过流操作, getExactSizeIfKnown()可能会返回-1, 所以默认大小为 ArrayList.DEFAULT_CAPACITY + final int sizeIfKnown = (int) Math.max(Math.min(keys.getExactSizeIfKnown(), values.getExactSizeIfKnown()), 10); + final List list = new ArrayList<>(sizeIfKnown); + // 保存第一个Spliterator的值 + final MutableObj key = new MutableObj<>(); + // 保存第二个Spliterator的值 + final MutableObj value = new MutableObj<>(); + // 当两个Spliterator中都还有剩余元素时 + while (keys.tryAdvance(key::set) && values.tryAdvance(value::set)) { + list.add(zipper.apply(key.get(), value.get())); + } + return EasyStream.of(list).parallel(isParallel()).onClose(stream()::close); + } + + /** + * 按指定长度切分为双层流 + *

+ * 形如:[1,2,3,4,5] -> [[1,2], [3,4], [5,6]] + *

+ * + * @param batchSize 指定长度, 正整数 + * @return 切好的流 + */ + default EasyStream> split(final int batchSize) { + final List list = this.collect(Collectors.toList()); + final int size = list.size(); + // 指定长度 大于等于 列表长度 + if (size <= batchSize) { + // 返回第一层只有单个元素的双层流,形如:[[1,2,3,4,5]] + return EasyStream.>of(EasyStream.of(list, isParallel())); + } + return EasyStream.iterate(0, i -> i < size, i -> i + batchSize) + .map(skip -> EasyStream.of(list.subList(skip, Math.min(size, skip + batchSize)), isParallel())) + .parallel(isParallel()) + .onClose(stream()::close); + } + + /** + * 按指定长度切分为元素为list的流 + *

+ * 形如:[1,2,3,4,5] -> [[1,2], [3,4], [5,6]] + *

+ * + * @param batchSize 指定长度, 正整数 + * @return 切好的流 + */ + default EasyStream> splitList(final int batchSize) { + return split(batchSize).map(EasyStream::toList); + } + + /** + * 将当前流转为键值对流 + * + * @param keyMapper 键的映射方法 + * @param valueMapper 值的映射方法 + * @param 键类型 + * @param 值类型 + * @return {@link EntryStream}实例 + */ + default EntryStream toEntries(Function keyMapper, Function valueMapper) { + Objects.requireNonNull(keyMapper); + Objects.requireNonNull(valueMapper); + return new EntryStream<>(map(t -> EntryStream.ofEntry(keyMapper.apply(t), valueMapper.apply(t)))); + } + + /** + * 将当前流转为键值对流 + * + * @param keyMapper 键的映射方法 + * @param 键类型 + * @return {@link EntryStream}实例 + */ + default EntryStream toEntries(Function keyMapper) { + return toEntries(keyMapper, Function.identity()); + } + + // region ============ generic ============ + + /** + * 反转顺序 + * + * @return 反转元素顺序 + */ + @SuppressWarnings("unchecked") + default S reverse() { + final T[] array = (T[]) toArray(); + ArrayUtil.reverse(array); + return wrapping(Stream.of(array)).parallel(isParallel()); + } + + /** + * 更改流的并行状态 + * + * @param parallel 是否并行 + * @return 流 + */ + default S parallel(final boolean parallel) { + return parallel ? parallel() : sequential(); + } + + /** + * 通过删除或替换现有元素或者原地添加新的元素来修改列表,并以列表形式返回被修改的内容。此方法不会改变原列表。 + * 类似js的splice函数 + * + * @param start 起始下标 + * @param deleteCount 删除个数,正整数 + * @param items 放入值 + * @return 操作后的流 + */ + default S splice(final int start, final int deleteCount, final T... items) { + final List elements = stream().collect(Collectors.toList()); + return wrapping(ListUtil.splice(elements, start, deleteCount, items).stream()) + .parallel(isParallel()); + } + + /** + *

遍历流中与断言匹配的元素,当遇到第一个不匹配的元素时终止,返回由匹配的元素组成的流。
+ * eg: + *

{@code
+	 * EasyStream.of(1, 2, 3, 4, 5)
+	 * 	.takeWhile(i -> Objects.equals(3, i)) // 获取元素,一直到遇到第一个3为止
+	 * 	.toList(); // = [1, 2]
+	 * }
+ * + *

与{@code JDK9}中的{@code takeWhile}方法不太一样,此操作为顺序且有状态的中间操作。 + * 即使在并行流中,该操作仍然是顺序执行的,并且不影响后续的并行操作: + *

{@code
+	 * EasyStream.iterate(1, i -> i + 1)
+	 * 	.parallel()
+	 * 	.takeWhile(e -> e < 50) // 顺序执行
+	 * 	.map(e -> e + 1) // 并发
+	 * 	.map(String::valueOf) // 并发
+	 * 	.toList();
+	 * }
+ * 若非必要,不推荐在并行流中进行该操作。 + * + * @param predicate 断言 + * @return 与指定断言匹配的元素组成的流 + */ + default S takeWhile(final Predicate predicate) { + Objects.requireNonNull(predicate); + return wrapping(StreamUtil.takeWhile(stream(), predicate)); + } + + /** + * <

删除流中与断言匹配的元素,当遇到第一个不匹配的元素时终止,返回由剩余不匹配的元素组成的流。
+ * eg: + *

{@code
+	 * EasyStream.of(1, 2, 3, 4, 5)
+	 * 	.dropWhile(i -> !Objects.equals(3, i)) // 删除不为3的元素,一直到遇到第一个3为止
+	 * 	.toList(); // = [3, 4, 5]
+	 * }
+ * + *

与{@code JDK9}中的{@code dropWhile}方法不太一样,此操作为顺序且有状态的中间操作。 + * 即使在并行流中,该操作仍然是顺序执行的,并且不影响后续的并行操作: + *

{@code
+	 * EasyStream.iterate(1, i -> i + 1)
+	 * 	.parallel()
+	 * 	.dropWhile(e -> e < 50) // 顺序执行
+	 * 	.map(e -> e + 1) // 并发
+	 * 	.map(String::valueOf) // 并发
+	 * 	.toList();
+	 * }
+ * 若非必要,不推荐在并行流中进行该操作。 + * + * @param predicate 断言 + * @return 剩余元素组成的流 + */ + default S dropWhile(final Predicate predicate) { + Objects.requireNonNull(predicate); + return wrapping(StreamUtil.dropWhile(stream(), predicate)); + } + + /** + * 返回一个具有去重特征的流 非并行流(顺序流)下对于重复元素,保留遇到顺序中最先出现的元素,并行流情况下不能保证具体保留哪一个 + * 这是一个有状态中间操作 + * + * @param 参数类型 + * @param keyExtractor 去重依据 + * @return 一个具有去重特征的流 + */ + default EasyStream distinct(final Function keyExtractor) { + Objects.requireNonNull(keyExtractor); + if (isParallel()) { + final ConcurrentHashMap exists = MapUtil.newConcurrentHashMap(); + // 标记是否出现过null值,用于保留第一个出现的null + // 由于ConcurrentHashMap的key不能为null,所以用此变量来标记 + final AtomicBoolean hasNull = new AtomicBoolean(false); + return EasyStream.of(stream().filter(e -> { + final F key = keyExtractor.apply(e); + if (key == null) { + // 已经出现过null值,跳过该值 + if (hasNull.get()) { + return false; + } + hasNull.set(Boolean.TRUE); + return true; + } else { + // 第一次出现的key返回true + return null == exists.putIfAbsent(key, Boolean.TRUE); + } + })).parallel(); + } else { + final Set exists = new HashSet<>(); + return EasyStream.of(stream().filter(e -> exists.add(keyExtractor.apply(e)))); + } + } + + // endregion + + // region ============ peek ============ + + /** + * 返回与指定函数将元素作为参数执行后组成的流。操作带下标,并行流时下标永远为-1 + * 这是一个无状态中间操作 + * @param action 指定的函数 + * @return 返回叠加操作后的FastStream + * @apiNote 该方法存在的意义主要是用来调试 + * 当你需要查看经过操作管道某处的元素和下标,可以执行以下操作: + *
{@code
+	 *     Stream.of("one", "two", "three", "four")
+	 * 				.filter(e -> e.length() > 3)
+	 * 				.peekIdx((e,i) -> System.out.println("Filtered value: " + e + " Filtered idx:" + i))
+	 * 				.map(String::toUpperCase)
+	 * 				.peekIdx((e,i) -> System.out.println("Mapped value: " + e + " Mapped idx:" + i))
+	 * 				.collect(Collectors.toList());
+	 * }
+ */ + default S peekIdx(BiConsumer action) { + Objects.requireNonNull(action); + if (isParallel()) { + return peek(e -> action.accept(e, NOT_FOUND_ELEMENT_INDEX)); + } else { + AtomicInteger index = new AtomicInteger(NOT_FOUND_ELEMENT_INDEX); + return peek(e -> action.accept(e, index.incrementAndGet())); + } + } + + /** + * 返回叠加调用{@link Console#log(Object)}打印出结果的流 + * + * @return 返回叠加操作后的FastStream + */ + default S log() { + return peek(Console::log); + } + + // endregion + + // region ============ concat ============ + + /** + * 与给定元素组成的流合并,成为新的流 + * + * @param obj 元素 + * @return 流 + */ + @SuppressWarnings("unchecked") + default S push(final T... obj) { + Stream result = stream(); + if (ArrayUtil.isNotEmpty(obj)) { + result = Stream.concat(stream(), Stream.of(obj)); + } + return wrapping(result); + } + + /** + * 给定元素组成的流与当前流合并,成为新的流 + * + * @param obj 元素 + * @return 流 + */ + default S unshift(final T... obj) { + Stream result = stream(); + if (ArrayUtil.isNotEmpty(obj)) { + result = Stream.concat(Stream.of(obj), stream()); + } + return wrapping(result); + } + + /** + * 将输入元素转为流,返回一个前半段为当前流,后半段为新流的新实例 + * + * @param iterable 集合 + * @return {@link EntryStream}实例 + */ + default S append(Iterable iterable) { + if (IterUtil.isEmpty(iterable)) { + return wrapping(this); + } + final Stream contacted = StreamSupport.stream(iterable.spliterator(), isParallel()); + return wrapping(Stream.concat(this, contacted)); + } + + /** + * 将输入元素转为流,返回一个前半段为新流,后半段为当前流的新实例 + * + * @param iterable 集合 + * @return {@link EntryStream}实例 + */ + default S prepend(Iterable iterable) { + if (IterUtil.isEmpty(iterable)) { + return wrapping(this); + } + final Stream contacted = StreamSupport.stream(iterable.spliterator(), isParallel()); + return wrapping(Stream.concat(contacted, this)); + } + + // endregion + + // region ============ filter ============ + + /** + * 过滤掉空元素 + * + * @return 过滤后的流 + */ + default S nonNull() { + return filter(Objects::nonNull); + } + + /** + * 过滤元素,返回与指定断言匹配的元素组成的流,断言带下标,并行流时下标永远为-1 + * 这是一个无状态中间操作 + * + * @param predicate 断言 + * @return 返回叠加过滤操作后的流 + */ + default S filterIdx(final BiPredicate predicate) { + Objects.requireNonNull(predicate); + if (isParallel()) { + return filter(e -> predicate.test(e, NOT_FOUND_ELEMENT_INDEX)); + } else { + final MutableInt index = new MutableInt(NOT_FOUND_ELEMENT_INDEX); + return filter(e -> predicate.test(e, index.incrementAndGet())); + } + } + + /** + * 过滤元素,返回与 指定操作结果 匹配 指定值 的元素组成的流 + * 这是一个无状态中间操作 + * + * @param 返回类型 + * @param mapper 操作 + * @param value 用来匹配的值 + * @return 与 指定操作结果 匹配 指定值 的元素组成的流 + */ + default S filter(final Function mapper, final R value) { + Objects.requireNonNull(mapper); + return filter(e -> Objects.equals(mapper.apply(e), value)); + } + + // endregion + + // region ============ flat ============ + + /** + * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作,返回多个流所有元素组成的流
+ * 这是一个无状态中间操作
+ * 例如,将users里所有user的id和parentId组合在一起,形成一个新的流: + *
{@code
+	 *     EasyStream ids = EasyStream.of(users).flatMap(user -> FastStream.of(user.getId(), user.getParentId()));
+	 * }
+ * + * @param mapper 操作,返回流 + * @param 拆分后流的元素类型 + * @return 返回叠加拆分操作后的流 + */ + @Override + default EasyStream flatMap(final Function> mapper) { + Objects.requireNonNull(mapper); + return new EasyStream<>(stream().flatMap(mapper)); + } + + /** + * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作,返回多个流所有元素组成的流,操作带下标,并行流时下标永远为-1 + * 这是一个无状态中间操作 + * + * @param mapper 操作,返回流 + * @param 拆分后流的元素类型 + * @return 返回叠加拆分操作后的流 + */ + default EasyStream flatMapIdx(final BiFunction> mapper) { + Objects.requireNonNull(mapper); + if (isParallel()) { + return flatMap(e -> mapper.apply(e, NOT_FOUND_ELEMENT_INDEX)); + } else { + final MutableInt index = new MutableInt(NOT_FOUND_ELEMENT_INDEX); + return flatMap(e -> mapper.apply(e, index.incrementAndGet())); + } + } + + /** + * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作, 转换为迭代器元素, + * 最后返回所有迭代器的所有元素组成的流
+ * 这是一个无状态中间操作
+ * 例如,将users里所有user的id和parentId组合在一起,形成一个新的流: + *
{@code
+	 *     EasyStream ids = EasyStream.of(users).flat(user -> FastStream.of(user.getId(), user.getParentId()));
+	 * }
+ * + * @param mapper 操作,返回可迭代对象 + * @param 拆分后流的元素类型 + * @return 返回叠加拆分操作后的流 + */ + default EasyStream flat(final Function> mapper) { + Objects.requireNonNull(mapper); + return flatMap(w -> EasyStream.of(mapper.apply(w))); + } + + /** + * 扩散流操作,可能影响流元素个数,对过滤后的非{@code null}元素执行mapper操作,转换为迭代器, + * 并过滤迭代器中为{@code null}的元素, 返回所有迭代器的所有非空元素组成的流
+ * 这是一个无状态中间操作
+ * + * @param mapper 操作,返回流 + * @param 拆分后流的元素类型 + * @return 返回叠加拆分操作后的流 + * @see #flat(Function) + * @see #nonNull() + */ + default EasyStream flatNonNull(final Function> mapper) { + return nonNull().flat(mapper).nonNull(); + } + + /** + * 将树递归扁平化为集合,内置一个小递归 + * 这是一个无状态中间操作
+ * eg: + *
{@code
+	 * List students = EasyStream.of(studentTree)
+	 * 	.flatTree(Student::getChildren, Student::setChildren)
+	 * 	.toList();
+	 * }
+ * + * @param childrenGetter 获取子节点的lambda,可以写作 {@code Student::getChildren} + * @param childrenSetter 设置子节点的lambda,可以写作 {@code Student::setChildren} + * @return EasyStream 一个流 + */ + default S flatTree(Function> childrenGetter, BiConsumer> childrenSetter) { + Objects.requireNonNull(childrenGetter); + Objects.requireNonNull(childrenSetter); + MutableObj>> recursiveRef = new MutableObj<>(); + Function> recursive = e -> EasyStream.of(childrenGetter.apply(e)) + .flat(recursiveRef.get()) + .unshift(e); + recursiveRef.set(recursive); + return wrapping(flatMap(recursive).peek(e -> childrenSetter.accept(e, null))); + } + + // endregion + + // region ============ map ============ + + /** + * 返回与指定函数将元素作为参数执行的结果组成的流 + * 这是一个无状态中间操作 + * + * @param mapper 指定的函数 + * @param 函数执行后返回的类型 + * @return 返回叠加操作后的流 + */ + @Override + default EasyStream map(final Function mapper) { + Objects.requireNonNull(mapper); + return new EasyStream<>(stream().map(mapper)); + } + + /** + * 返回 元素 转换后 并且不为 {@code null} 的 新元素组成的流
+ * 这是一个无状态中间操作
+ *
{@code
+	 * // 等价于先调用map再调用nonNull
+	 * .nonNull().map(...).nonNull()...
+	 * }
+ * + * @param mapper 指定的函数 + * @param 函数执行后返回的类型 + * @return 新元素组成的流 + */ + default EasyStream mapNonNull(final Function mapper) { + Objects.requireNonNull(mapper); + return new EasyStream<>(nonNull().map(mapper).nonNull()); + } + + /** + * 返回与指定函数将元素作为参数执行的结果组成的流,操作带下标,并行流时下标永远为-1 + * 这是一个无状态中间操作 + * + * @param mapper 指定的函数 + * @param 函数执行后返回的类型 + * @return 返回叠加操作后的流 + */ + default EasyStream mapIdx(final BiFunction mapper) { + Objects.requireNonNull(mapper); + if (isParallel()) { + return map(e -> mapper.apply(e, NOT_FOUND_ELEMENT_INDEX)); + } else { + final MutableInt index = new MutableInt(NOT_FOUND_ELEMENT_INDEX); + return map(e -> mapper.apply(e, index.incrementAndGet())); + } + } + + /** + * 扩散流操作,可能影响流元素个数,将原有流元素执行mapper操作,返回多个流所有元素组成的流,操作带一个方法,调用该方法可增加元素 + * 这是一个无状态中间操作 + * + * @param mapper 操作,返回流 + * @param 拆分后流的元素类型 + * @return 返回叠加拆分操作后的流 + */ + default EasyStream mapMulti(final BiConsumer> mapper) { + Objects.requireNonNull(mapper); + return flatMap(e -> { + final EasyStream.Builder buffer = EasyStream.builder(); + mapper.accept(e, buffer); + return buffer.build(); + }); + } + + // endregion + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/StreamWrapper.java b/hutool-core/src/main/java/cn/hutool/core/stream/WrappedStream.java similarity index 87% rename from hutool-core/src/main/java/cn/hutool/core/stream/StreamWrapper.java rename to hutool-core/src/main/java/cn/hutool/core/stream/WrappedStream.java index 8c6519f66..545797323 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/StreamWrapper.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/WrappedStream.java @@ -1,41 +1,33 @@ package cn.hutool.core.stream; -import cn.hutool.core.util.ObjUtil; - -import java.util.Comparator; -import java.util.Iterator; -import java.util.Optional; -import java.util.Spliterator; +import java.util.*; import java.util.function.*; import java.util.stream.*; /** - *

表示一个用于增强原始{@link Stream}对象的包装器,当调用{@link Stream}中的方法时, - * 将会代理到被包装的原始流对象,并返回指定的包装器实例。 + *

{@link Stream}实例的包装器,用于增强原始的{@link Stream},提供一些额外的中间与终端操作
+ * 默认提供两个可用实现: + *

    + *
  • {@link EasyStream}:针对单元素的通用增强流实现;
  • + *
  • {@link EntryStream}:针对键值对类型元素的增强流实现;
  • + *
* * @param 流中的元素类型 - * @param 链式调用获得的实现类类型 + * @param {@link WrappedStream}的实现类类型 * @author huangchengxing - * @see TerminableStreamWrapper - * @see TransformableStreamWrapper - * @see AbstractEnhancedStreamWrapper + * @see TerminableWrappedStream + * @see TransformableWrappedStream + * @see AbstractEnhancedWrappedStream + * @see EasyStream + * @see EntryStream + * @since 6.0.0 */ -public interface StreamWrapper> extends Stream, Iterable { +public interface WrappedStream> extends Stream, Iterable { /** * 代表不存在的下标, 一般用于并行流的下标, 或者未找到元素时的下标 */ - int NOT_FOUND_INDEX = -1; - - /** - * 将一个流包装为简单增强流,若{@code stream}为{@code null}则默认返回一个空串行流 - * - * @param stream 被包装的流 - * @return {@link SimpleStreamWrapper}实例 - */ - static SimpleStreamWrapper create(Stream stream) { - return new SimpleStreamWrapper<>(ObjUtil.defaultIfNull(stream, Stream.empty())); - } + int NOT_FOUND_ELEMENT_INDEX = -1; /** * 获取被包装的原始流 @@ -48,9 +40,9 @@ public interface StreamWrapper> extends Stream< * 将一个原始流包装为指定类型的增强流 * * @param source 被包装的流 - * @return I + * @return S */ - I wrapping(Stream source); + S wrapping(Stream source); /** * 过滤元素,返回与指定断言匹配的元素组成的流 @@ -60,7 +52,8 @@ public interface StreamWrapper> extends Stream< * @return 返回叠加过滤操作后的流 */ @Override - default I filter(Predicate predicate) { + default S filter(Predicate predicate) { + Objects.requireNonNull(predicate); return wrapping(stream().filter(predicate)); } @@ -73,6 +66,7 @@ public interface StreamWrapper> extends Stream< */ @Override default IntStream mapToInt(ToIntFunction mapper) { + Objects.requireNonNull(mapper); return stream().mapToInt(mapper); } @@ -85,6 +79,7 @@ public interface StreamWrapper> extends Stream< */ @Override default LongStream mapToLong(ToLongFunction mapper) { + Objects.requireNonNull(mapper); return stream().mapToLong(mapper); } @@ -97,6 +92,7 @@ public interface StreamWrapper> extends Stream< */ @Override default DoubleStream mapToDouble(ToDoubleFunction mapper) { + Objects.requireNonNull(mapper); return stream().mapToDouble(mapper); } @@ -109,6 +105,7 @@ public interface StreamWrapper> extends Stream< */ @Override default IntStream flatMapToInt(Function mapper) { + Objects.requireNonNull(mapper); return stream().flatMapToInt(mapper); } @@ -121,6 +118,7 @@ public interface StreamWrapper> extends Stream< */ @Override default LongStream flatMapToLong(Function mapper) { + Objects.requireNonNull(mapper); return stream().flatMapToLong(mapper); } @@ -133,6 +131,7 @@ public interface StreamWrapper> extends Stream< */ @Override default DoubleStream flatMapToDouble(Function mapper) { + Objects.requireNonNull(mapper); return stream().flatMapToDouble(mapper); } @@ -143,7 +142,7 @@ public interface StreamWrapper> extends Stream< * @return 一个具有去重特征的流 */ @Override - default I distinct() { + default S distinct() { return wrapping(stream().distinct()); } @@ -156,7 +155,7 @@ public interface StreamWrapper> extends Stream< * @return 一个元素按自然顺序排序的流 */ @Override - default I sorted() { + default S sorted() { return wrapping(stream().sorted()); } @@ -170,7 +169,8 @@ public interface StreamWrapper> extends Stream< * @return 一个元素按指定的Comparator排序的流 */ @Override - default I sorted(Comparator comparator) { + default S sorted(Comparator comparator) { + Objects.requireNonNull(comparator); return wrapping(stream().sorted(comparator)); } @@ -192,7 +192,8 @@ public interface StreamWrapper> extends Stream< * } */ @Override - default I peek(Consumer action) { + default S peek(Consumer action) { + Objects.requireNonNull(action); return wrapping(stream().peek(action)); } @@ -204,7 +205,7 @@ public interface StreamWrapper> extends Stream< * @return 截取后的流 */ @Override - default I limit(long maxSize) { + default S limit(long maxSize) { return wrapping(stream().limit(maxSize)); } @@ -216,7 +217,7 @@ public interface StreamWrapper> extends Stream< * @return 丢弃前面n个元素后的剩余元素组成的流 */ @Override - default I skip(long n) { + default S skip(long n) { return wrapping(stream().skip(n)); } @@ -228,6 +229,7 @@ public interface StreamWrapper> extends Stream< */ @Override default void forEach(Consumer action) { + Objects.requireNonNull(action); stream().forEach(action); } @@ -239,6 +241,7 @@ public interface StreamWrapper> extends Stream< */ @Override default void forEachOrdered(Consumer action) { + Objects.requireNonNull(action); stream().forEachOrdered(action); } @@ -264,6 +267,7 @@ public interface StreamWrapper> extends Stream< */ @Override default A[] toArray(IntFunction generator) { + Objects.requireNonNull(generator); return stream().toArray(generator); } @@ -289,6 +293,7 @@ public interface StreamWrapper> extends Stream< */ @Override default T reduce(T identity, BinaryOperator accumulator) { + Objects.requireNonNull(accumulator); return stream().reduce(identity, accumulator); } @@ -324,6 +329,7 @@ public interface StreamWrapper> extends Stream< */ @Override default Optional reduce(BinaryOperator accumulator) { + Objects.requireNonNull(accumulator); return stream().reduce(accumulator); } @@ -341,6 +347,8 @@ public interface StreamWrapper> extends Stream< */ @Override default U reduce(U identity, BiFunction accumulator, BinaryOperator combiner) { + Objects.requireNonNull(accumulator); + Objects.requireNonNull(combiner); return stream().reduce(identity, accumulator, combiner); } @@ -359,6 +367,9 @@ public interface StreamWrapper> extends Stream< */ @Override default R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) { + Objects.requireNonNull(supplier); + Objects.requireNonNull(accumulator); + Objects.requireNonNull(combiner); return stream().collect(supplier, accumulator, combiner); } @@ -373,6 +384,7 @@ public interface StreamWrapper> extends Stream< */ @Override default R collect(Collector collector) { + Objects.requireNonNull(collector); return stream().collect(collector); } @@ -384,6 +396,7 @@ public interface StreamWrapper> extends Stream< */ @Override default Optional min(Comparator comparator) { + Objects.requireNonNull(comparator); return stream().min(comparator); } @@ -395,6 +408,7 @@ public interface StreamWrapper> extends Stream< */ @Override default Optional max(Comparator comparator) { + Objects.requireNonNull(comparator); return stream().max(comparator); } @@ -416,6 +430,7 @@ public interface StreamWrapper> extends Stream< */ @Override default boolean anyMatch(Predicate predicate) { + Objects.requireNonNull(predicate); return stream().anyMatch(predicate); } @@ -427,6 +442,7 @@ public interface StreamWrapper> extends Stream< */ @Override default boolean allMatch(Predicate predicate) { + Objects.requireNonNull(predicate); return stream().allMatch(predicate); } @@ -438,6 +454,7 @@ public interface StreamWrapper> extends Stream< */ @Override default boolean noneMatch(Predicate predicate) { + Objects.requireNonNull(predicate); return stream().noneMatch(predicate); } @@ -497,7 +514,7 @@ public interface StreamWrapper> extends Stream< * @return 串行流 */ @Override - default I sequential() { + default S sequential() { return wrapping(stream().sequential()); } @@ -507,7 +524,7 @@ public interface StreamWrapper> extends Stream< * @return 并行流 */ @Override - default I parallel() { + default S parallel() { return wrapping(stream().parallel()); } @@ -518,7 +535,7 @@ public interface StreamWrapper> extends Stream< * @return 无序流 */ @Override - default I unordered() { + default S unordered() { return wrapping(stream().unordered()); } @@ -529,7 +546,7 @@ public interface StreamWrapper> extends Stream< * @return 流 */ @Override - default I onClose(Runnable closeHandler) { + default S onClose(Runnable closeHandler) { return wrapping(stream().onClose(closeHandler)); } diff --git a/hutool-core/src/test/java/cn/hutool/core/stream/AbstractEnhancedWrappedStreamTest.java b/hutool-core/src/test/java/cn/hutool/core/stream/AbstractEnhancedWrappedStreamTest.java new file mode 100644 index 000000000..a509d872b --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/stream/AbstractEnhancedWrappedStreamTest.java @@ -0,0 +1,705 @@ +package cn.hutool.core.stream; + +import cn.hutool.core.collection.ListUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.junit.Assert; +import org.junit.Test; + +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; +import java.util.stream.*; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; + +/** + * {@link AbstractEnhancedWrappedStream}、{@link TerminableWrappedStream}、{@link TransformableWrappedStream}的测试用例。 + * 此用例用于保证通过{@link AbstractEnhancedWrappedStream}获得的默认方法,在子类不重写的情况下能够按照预期效果生效 + * + * @author huangchengxing + */ +public class AbstractEnhancedWrappedStreamTest { + + @Test + public void testToList() { + List list = asList(1, 2, 3); + List toList = wrap(list).toList(); + Assert.assertEquals(list, toList); + } + + @Test + public void testToUnmodifiableList() { + List list = wrap(1, 2, 3) + .toUnmodifiableList(); + Assert.assertThrows(UnsupportedOperationException.class, () -> list.remove(0)); + } + + @Test + public void testToSet() { + List list = asList(1, 2, 3); + Set toSet = wrap(list).map(String::valueOf).toSet(); + Assert.assertEquals(new HashSet<>(asList("1", "2", "3")), toSet); + } + + @Test + public void testToUnmodifiableSet() { + Set set = wrap(1, 2, 3) + .toUnmodifiableSet(); + Assert.assertThrows(UnsupportedOperationException.class, () -> set.remove(0)); + } + + @Test + public void testToCollection() { + List list = asList(1, 2, 3); + List toCollection = wrap(list).map(String::valueOf).toColl(LinkedList::new); + Assert.assertEquals(asList("1", "2", "3"), toCollection); + } + + @Test + public void testToMap() { + List list = asList(1, 2, 3); + Map identityMap = wrap(list).toMap(String::valueOf); + Assert.assertEquals(new HashMap() {{ + put("1", 1); + put("2", 2); + put("3", 3); + }}, identityMap); + } + + @Test + public void testToUnmodifiableMap() { + Map map1 = wrap(1, 2, 3).toUnmodifiableMap(Function.identity(), Function.identity()); + Assert.assertThrows(UnsupportedOperationException.class, () -> map1.remove(1)); + Map map2 = wrap(1, 2, 3).toUnmodifiableMap(Function.identity(), Function.identity(), (t1, t2) -> t1); + Assert.assertThrows(UnsupportedOperationException.class, () -> map2.remove(1)); + } + + @Test + public void testToZip() { + List orders = asList(1, 2, 3); + List list = asList("dromara", "hutool", "sweet"); + Map toZip = wrap(orders).toZip(list); + Assert.assertEquals(new HashMap() {{ + put(1, "dromara"); + put(2, "hutool"); + put(3, "sweet"); + }}, toZip); + } + + @Test + public void testTransform() { + List list = wrap(1, 2, 3).transform(Wrapper::toList).orElse(null); + Assert.assertEquals(asList(1, 2, 3), list); + } + + @Test + public void testFindFirst() { + List list = asList(1, 2, 3); + Assert.assertEquals((Integer)1, wrap(list).findFirst(t -> (t & 1) == 1).orElse(null)); + Assert.assertEquals((Integer)1, wrap(list).filter(t -> (t & 1) == 1).findFirst().orElse(null)); + } + + @Test + public void testFindFirstIdx() { + List list = asList(1, 2, 3); + Assert.assertEquals(1, wrap(list).findFirstIdx(t -> (t & 1) == 0)); + } + + @Test + public void testFindLast() { + List list = asList(1, 2, 3); + Assert.assertEquals((Integer)3, wrap(list).findLast(t -> (t & 1) == 1).orElse(null)); + } + + @Test + public void testFindLastIdx() { + List list = asList(1, 2, 3); + Assert.assertEquals(1, wrap(list).findLastIdx(t -> (t & 1) == 0)); + } + + @Test + public void testAt() { + List list = asList(1, 2, 3); + Assert.assertEquals((Integer)3, wrap(list).at(2).orElse(null)); + } + + @Test + public void testIsEmpty() { + Assert.assertTrue(wrap(Collections.emptyList()).isEmpty()); + Assert.assertFalse(wrap(asList(1, 2, 3)).isEmpty()); + } + + @Test + public void testIsNotEmpty() { + Assert.assertFalse(wrap(Collections.emptyList()).isNotEmpty()); + Assert.assertTrue(wrap(asList(1, 2, 3)).isNotEmpty()); + } + + @Test + public void testJoining() { + List list = asList(1, 2, 3); + String joining = wrap(list).join(); + Assert.assertEquals("123", joining); + Assert.assertEquals("1,2,3", wrap(list).join(",")); + Assert.assertEquals("(1,2,3)", wrap(list).join(",", "(", ")")); + } + + @Test + public void testGrouping() { + List list = asList(1, 2, 3); + Map> map = new HashMap>() {{ + put("1", singletonList(1)); + put("2", singletonList(2)); + put("3", singletonList(3)); + }}; + + Map> group = wrap(list).group(String::valueOf, HashMap::new, Collectors.toList()); + Assert.assertEquals(map, group); + group = wrap(list).group(String::valueOf, Collectors.toList()); + Assert.assertEquals(map, group); + group = wrap(list).group(String::valueOf); + Assert.assertEquals(map, group); + } + + @Test + public void testPartitioning() { + List list = asList(1, 2, 3); + Map> map = new HashMap>() {{ + put(Boolean.TRUE, singletonList(2)); + put(Boolean.FALSE, asList(1, 3)); + }}; + + Map> partition = wrap(list).partitioning(t -> (t & 1) == 0, Collectors.toList()); + Assert.assertEquals(map, partition); + partition = wrap(list).partitioning(t -> (t & 1) == 0); + Assert.assertEquals(map, partition); + } + + @Test + public void testForEachIdx() { + List elements = new ArrayList<>(); + List indexes = new ArrayList<>(); + wrap(1, 2, 3).forEachIdx((t, i) -> { + elements.add(t); + indexes.add(i); + }); + Assert.assertEquals(asList(1, 2, 3), elements); + Assert.assertEquals(asList(0, 1, 2), indexes); + } + + @Test + public void testForEachOrderedIdx() { + List elements = new ArrayList<>(); + List indexes = new ArrayList<>(); + wrap(1, 2, 3).forEachOrderedIdx((t, i) -> { + elements.add(t); + indexes.add(i); + }); + Assert.assertEquals(asList(1, 2, 3), elements); + Assert.assertEquals(asList(0, 1, 2), indexes); + } + + @Test + public void testForEachOrdered() { + List elements = new ArrayList<>(); + wrap(1, 2, 3).forEachOrdered(elements::add); + Assert.assertEquals(asList(1, 2, 3), elements); + } + + @Test + public void testForEach() { + List elements = new ArrayList<>(); + wrap(1, 2, 3).forEach(elements::add); + Assert.assertEquals(asList(1, 2, 3), elements); + } + + @Test + public void testMapToInt() { + int[] array = wrap(1, 2, 3).mapToInt(Integer::intValue).toArray(); + Assert.assertArrayEquals(new int[] {1, 2, 3}, array); + } + + @Test + public void testMapToLong() { + long[] array = wrap(1L, 2L, 3L).mapToLong(Long::intValue).toArray(); + Assert.assertArrayEquals(new long[] {1L, 2L, 3L}, array); + } + + @Test + public void testMapToDouble() { + double[] array = wrap(1d, 2d, 3d).mapToDouble(Double::intValue).toArray(); + Assert.assertEquals(1d, array[0], 0.01); + Assert.assertEquals(2d, array[1], 0.01); + Assert.assertEquals(3d, array[2], 0.01); + } + + @Test + public void testFlatMapToInt() { + int[] array = wrap(1, 2, 3).flatMapToInt(IntStream::of).toArray(); + Assert.assertArrayEquals(new int[] {1, 2, 3}, array); + } + + @Test + public void testFlatMapToLong() { + long[] array = wrap(1L, 2L, 3L).flatMapToLong(LongStream::of).toArray(); + Assert.assertArrayEquals(new long[] {1L, 2L, 3L}, array); + } + + @Test + public void testFlatMapToDouble() { + double[] array = wrap(1d, 2d, 3d).flatMapToDouble(DoubleStream::of).toArray(); + Assert.assertEquals(1d, array[0], 0.01); + Assert.assertEquals(2d, array[1], 0.01); + Assert.assertEquals(3d, array[2], 0.01); + } + + @Test + public void testSorted() { + List list = wrap(3, 1, 2).sorted().toList(); + Assert.assertEquals(asList(1, 2, 3), list); + } + + @Test + public void testPeek() { + List elements = new ArrayList<>(); + wrap(1, 2, 3).peek(elements::add).exec(); + Assert.assertEquals(asList(1, 2, 3), elements); + } + + @Test + public void testPeekIdx() { + List elements = new ArrayList<>(); + List indexes = new ArrayList<>(); + wrap(1, 2, 3).peekIdx((t, i) -> { + elements.add(t); + indexes.add(i); + }).exec(); + Assert.assertEquals(asList(1, 2, 3), elements); + Assert.assertEquals(asList(0, 1, 2), indexes); + + Set elements2 = new HashSet<>(); + Set indexes2 = new HashSet<>(); + wrap(1, 2, null).parallel().peekIdx((t, i) -> { + elements2.add(t); + indexes2.add(i); + }).exec(); + Assert.assertEquals(new HashSet<>(asList(1, null, 2)), elements2); + Assert.assertEquals(new HashSet<>(asList(-1, -1, -1)), indexes2); + } + + @Test + public void testLimit() { + List list = wrap(1, 2, 3).limit(2L).toList(); + Assert.assertEquals(asList(1, 2), list); + } + + @Test + public void testSkip() { + List list = wrap(1, 2, 3).skip(1L).toList(); + Assert.assertEquals(asList(2, 3), list); + } + + @Test + public void testToArray() { + Object[] array1 = wrap(1, 2, 3).toArray(); + Assert.assertArrayEquals(new Object[]{1, 2, 3}, array1); + array1 = wrap(1, 2, 3).toArray(Object[]::new); + Assert.assertArrayEquals(new Object[]{1, 2, 3}, array1); + } + + @Test + public void testReduce() { + Assert.assertEquals((Integer)6, wrap(1, 2, 3).reduce(Integer::sum).orElse(null)); + Assert.assertEquals((Integer)6, wrap(1, 2, 3).reduce(0, Integer::sum)); + Assert.assertEquals((Integer)6, wrap(1, 2, 3).reduce(0, Integer::sum, Integer::sum)); + } + + @Test + public void testCollect() { + Assert.assertEquals(asList(1, 2, 3), wrap(1, 2, 3).collect(Collectors.toList())); + Assert.assertEquals( + asList(1, 2, 3), + wrap(1, 2, 3).collect(ArrayList::new, List::add, List::addAll) + ); + } + + @Test + public void testMin() { + Assert.assertEquals((Integer)1, wrap(1, 2, 3).min(Comparator.comparingInt(Integer::intValue)).orElse(null)); + } + + @Test + public void testMax() { + Assert.assertEquals((Integer)3, wrap(1, 2, 3).max(Comparator.comparingInt(Integer::intValue)).orElse(null)); + } + + @Test + public void testCount() { + Assert.assertEquals(3, wrap(1, 2, 3).count()); + } + + @Test + public void testAnyMatch() { + Assert.assertTrue(wrap(1, 2, 3).anyMatch(t -> (t & 1) == 0)); + Assert.assertFalse(wrap(1, 3).anyMatch(t -> (t & 1) == 0)); + } + + @Test + public void testAllMatch() { + Assert.assertFalse(wrap(1, 2, 3).allMatch(t -> (t & 1) == 0)); + Assert.assertTrue(wrap(2, 4).anyMatch(t -> (t & 1) == 0)); + } + + @Test + public void testNoneMatch() { + Assert.assertFalse(wrap(1, 2, 3).noneMatch(t -> (t & 1) == 0)); + Assert.assertTrue(wrap(1, 3).noneMatch(t -> (t & 1) == 0)); + } + + @Test + public void testFindAny() { + Assert.assertNotNull(wrap(1, 2, 3).findAny()); + } + + @Test + public void testIterator() { + Iterator iter1 = Stream.of(1, 2, 3).iterator(); + Iterator iter2 = wrap(1, 2, 3).iterator(); + while (iter1.hasNext() && iter2.hasNext()) { + Assert.assertEquals(iter1.next(), iter2.next()); + } + } + + @Test + public void testSpliterator() { + Spliterator iter1 = Stream.of(1, 2, 3).spliterator(); + Spliterator iter2 = wrap(1, 2, 3).spliterator(); + Assert.assertEquals(iter1.trySplit().estimateSize(), iter2.trySplit().estimateSize()); + } + + @Test + public void testIsParallel() { + Assert.assertTrue(wrap(Stream.of(1, 2, 3).parallel()).isParallel()); + } + + @Test + public void testSequential() { + Assert.assertFalse(wrap(Stream.of(1, 2, 3).parallel()).sequential().isParallel()); + } + + @Test + public void testUnordered() { + Assert.assertNotNull(wrap(Stream.of(1, 2, 3)).unordered()); + } + + @Test + public void testOnClose() { + AtomicBoolean atomicBoolean = new AtomicBoolean(false); + wrap(Stream.of(1, 2, 3).onClose(() -> atomicBoolean.set(true))).close(); + Assert.assertTrue(atomicBoolean.get()); + } + + @Test + public void testClose() { + Wrapper stream = wrap(Stream.of(1, 2, 3)); + stream.close(); + Assert.assertThrows(IllegalStateException.class, stream::exec); + } + + @Test + public void testReverse() { + Assert.assertEquals( + asList(3, 2, 1), wrap(1, 2, 3).reverse().toList() + ); + } + + @Test + public void testParallel() { + Assert.assertTrue(wrap(1, 2, 3).parallel().isParallel()); + } + + @Test + public void testSplice() { + Assert.assertEquals( + asList(1, 4, 5), wrap(1, 2, 3).splice(1, 2, 4, 5).toList() + ); + } + + @Test + public void testTakeWhile() { + Assert.assertEquals( + asList(1, 2), + wrap(1, 2, 3, 4).takeWhile(i -> !Objects.equals(i, 3)).toList() + ); + + } + + @Test + public void testDropWhile() { + Assert.assertEquals( + asList(3, 4), + wrap(1, 2, 3, 4).dropWhile(i -> !Objects.equals(i, 3)).toList() + ); + } + + @Test + public void testDistinct() { + Assert.assertEquals( + asList(1, 2, 3), wrap(1, 1, 2, 3).distinct().toList() + ); + } + + @Test + public void testLog() { + Assert.assertNotNull(wrap(1, 2, 3).log().toList()); + } + + @Test + public void testPush() { + Assert.assertEquals( + asList(1, 2, 3), wrap(1).push(2, 3).toList() + ); + } + + @Test + public void testUnshift() { + Assert.assertEquals( + asList(1, 2, 3), wrap(3).unshift(1, 2).toList() + ); + } + + @Test + public void testAppend() { + Assert.assertEquals( + asList(1, 2, 3), wrap(1).append(asList(2, 3)).toList() + ); + Assert.assertEquals( + asList(1, 2, 3), wrap(1, 2, 3).append(null).toList() + ); + } + + @Test + public void testPrepend() { + Assert.assertEquals( + asList(1, 2, 3), wrap(3).prepend(asList(1, 2)).toList() + ); + Assert.assertEquals( + asList(1, 2, 3), wrap(1, 2, 3).prepend(null).toList() + ); + } + + @Test + public void testNonNull() { + Assert.assertEquals( + asList(1, 3), wrap(1, null, 3).nonNull().toList() + ); + } + + @Test + public void testFilterIdx() { + List indexes = new ArrayList<>(); + Assert.assertEquals( + asList(1, 3), + wrap(1, 2, 3).filterIdx((t, i) -> { + indexes.add(i); + return (t & 1) == 1; + }).toList() + ); + Assert.assertEquals(asList(0, 1, 2), indexes); + } + + @Test + public void testFilter() { + Assert.assertEquals( + asList(1, 3), wrap(1, 2, 3).filter(i -> (i & 1) == 1).toList() + ); + } + + @Test + public void testFlatMap() { + Assert.assertEquals( + asList(1, 2, 3), wrap(1, 2, 3).flatMap(Stream::of).toList() + ); + } + + @Test + public void testFlatMapIdx() { + List indexes = new ArrayList<>(); + Assert.assertEquals( + asList(1, 2, 3), wrap(1, 2, 3).flatMapIdx((t, i) -> { + indexes.add(i); + return Stream.of(t); + }).toList() + ); + Assert.assertEquals(asList(0, 1, 2), indexes); + } + + @Test + public void testFlat() { + Assert.assertEquals( + asList(1, 2, 3), wrap(1, 2, 3).flat(Collections::singletonList).toList() + ); + } + + @Test + public void testFlatNonNull() { + Assert.assertEquals( + asList(2, 3), wrap(null, 2, 3).flatNonNull(Collections::singletonList).toList() + ); + } + + @Test + public void testFlatTree() { + Tree root = new Tree(1, asList(new Tree(2, asList(new Tree(3, Collections.emptyList()))))); + Assert.assertEquals(3L, wrap(root).flatTree(Tree::getChildren, Tree::setChildren).count()); + } + + @Test + public void testMap() { + Assert.assertEquals( + asList("1", "2", "3"), wrap(1, 2, 3).map(String::valueOf).toList() + ); + } + + @Test + public void testMapNonNull() { + Assert.assertEquals( + asList("3"), wrap(null, 2, 3, 4).mapNonNull(t -> ((t & 1) == 0) ? null : String.valueOf(t)).toList() + ); + } + + @Test + public void testMapIdx() { + List indexes = new ArrayList<>(); + Assert.assertEquals( + asList("1", "2", "3"), wrap(1, 2, 3).mapIdx((t, i) -> { + indexes.add(i); + return String.valueOf(t); + }).toList() + ); + Assert.assertEquals(asList(0, 1, 2), indexes); + } + + @Test + public void testMapMulti() { + Assert.assertEquals( + asList(1, 2, 3), + wrap(1, 2, 3).mapMulti((t, builder) -> { + builder.accept(t); + }).toList() + ); + } + + @Test + public void testHashCode() { + Stream stream = Stream.of(1, 2, 3); + Assert.assertEquals(stream.hashCode(), wrap(stream).hashCode()); + } + + @Test + public void testEquals() { + Stream stream = Stream.of(1, 2, 3); + Assert.assertEquals(wrap(stream), stream); + } + + @Test + public void testToString() { + Stream stream = Stream.of(1, 2, 3); + Assert.assertEquals(stream.toString(), wrap(stream).toString()); + } + + @Test + public void testToEntries() { + Map expect = new HashMap(){{ + put(1, 1); + put(2, 2); + put(3, 3); + }}; + Map map = EasyStream.of(1, 2, 3) + .toEntries(Function.identity(), Function.identity()) + .toMap(); + Assert.assertEquals(expect, map); + + map = EasyStream.of(1, 2, 3) + .toEntries(Function.identity()) + .toMap(); + Assert.assertEquals(expect, map); + } + + @Test + public void testZip() { + final List orders = Arrays.asList(1, 2, 3); + final List list = Arrays.asList("dromara", "hutool", "sweet"); + List zip = wrap(orders).zip(list, (e1, e2) -> e1 + "." + e2).toList(); + Assert.assertEquals(Arrays.asList("1.dromara", "2.hutool", "3.sweet"), zip); + + zip = wrap((Stream)EasyStream.iterate(1, i -> i + 1)).zip(list, (e1, e2) -> e1 + "." + e2).toList(); + Assert.assertEquals(Arrays.asList("1.dromara", "2.hutool", "3.sweet"), zip); + } + + @Test + public void testListSplit() { + final List list = Arrays.asList(1, 2, 3, 4, 5); + List> lists = wrap(list).split(2).map(TerminableWrappedStream::toList).toList(); + Assert.assertEquals(ListUtil.split(list, 2), lists); + + // 指定长度 大于等于 列表长度 + lists = wrap(list).split(list.size()).map(TerminableWrappedStream::toList).toList(); + Assert.assertEquals(singletonList(list), lists); + } + + @Test + public void testSplitList() { + final List list = Arrays.asList(1, 2, 3, 4, 5); + List> lists = wrap(list).splitList(2).toList(); + Assert.assertEquals(ListUtil.split(list, 2), lists); + + // 指定长度 大于等于 列表长度 + lists = wrap(list).splitList(list.size()).toList(); + Assert.assertEquals(singletonList(list), lists); + } + + @SafeVarargs + private static Wrapper wrap(T... array) { + return new Wrapper<>(Stream.of(array)); + } + + private static Wrapper wrap(Iterable iterable) { + return new Wrapper<>(StreamSupport.stream(iterable.spliterator(), false)); + } + + private static Wrapper wrap(Stream stream) { + return new Wrapper<>(stream); + } + + private static class Wrapper extends AbstractEnhancedWrappedStream> { + + /** + * 创建一个流包装器 + * + * @param stream 包装的流对象 + * @throws NullPointerException 当{@code stream}为{@code null}时抛出 + */ + protected Wrapper(Stream stream) { + super(stream); + } + + @Override + public Wrapper wrapping(Stream source) { + return new Wrapper<>(source); + } + + } + + @Setter + @Getter + @AllArgsConstructor + private static class Tree { + private final Integer id; + private List children; + } + +} 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 6312d17e3..e1386591b 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 @@ -1,9 +1,7 @@ package cn.hutool.core.stream; - import cn.hutool.core.collection.ListUtil; import cn.hutool.core.map.MapUtil; -import lombok.Builder; import lombok.Data; import lombok.experimental.Tolerate; import org.junit.Assert; @@ -22,6 +20,13 @@ import static java.util.Collections.singletonList; */ public class EasyStreamTest { + @Test + public void testConcat() { + Stream stream1 = Stream.of(1, 2); + Stream stream2 = Stream.of(3, 4); + Assert.assertEquals(4, EasyStream.concat(stream1, stream2).count()); + } + @Test public void testBuilder() { final List list = EasyStream.builder().add(1).add(2).add(3).build().toList(); @@ -54,7 +59,7 @@ public class EasyStreamTest { } @Test - public void testToCollection() { + public void testToColl() { final List list = Arrays.asList(1, 2, 3); final List toCollection = EasyStream.of(list).map(String::valueOf).toColl(LinkedList::new); Assert.assertEquals(Arrays.asList("1", "2", "3"), toCollection); @@ -168,18 +173,22 @@ public class EasyStreamTest { final List collect1 = list.stream().distinct().collect(Collectors.toList()); final List collect2 = list.stream().parallel().distinct().collect(Collectors.toList()); - // 使用FastStream去重 + // 使用EasyStream去重 final List distinctBy1 = EasyStream.of(list).distinct().toList(); final List distinctBy2 = EasyStream.of(list).parallel().distinct(String::valueOf).toList(); Assert.assertEquals(collect1, distinctBy1); 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()).count() + ); } @Test public void testForeachIdx() { final List list = Arrays.asList("dromara", "hutool", "sweet"); - final EasyStream.FastStreamBuilder builder = EasyStream.builder(); + final EasyStream.Builder builder = EasyStream.builder(); EasyStream.of(list).forEachIdx((e, i) -> builder.accept(i + 1 + "." + e)); Assert.assertEquals(Arrays.asList("1.dromara", "2.hutool", "3.sweet"), builder.build().toList()); // 并行流时为-1 @@ -189,11 +198,11 @@ public class EasyStreamTest { @Test public void testForEachOrderedIdx() { final List list = Arrays.asList("dromara", "hutool", "sweet"); - final EasyStream.FastStreamBuilder builder = EasyStream.builder(); + final EasyStream.Builder builder = EasyStream.builder(); EasyStream.of(list).forEachOrderedIdx((e, i) -> builder.accept(i + 1 + "." + e)); Assert.assertEquals(Arrays.asList("1.dromara", "2.hutool", "3.sweet"), builder.build().toList()); - final EasyStream.FastStreamBuilder streamBuilder = EasyStream.builder(); + final EasyStream.Builder streamBuilder = EasyStream.builder(); EasyStream.of(list).parallel().forEachOrderedIdx((e, i) -> streamBuilder.accept(i + 1 + "." + e)); Assert.assertEquals(Arrays.asList("0.dromara", "0.hutool", "0.sweet"), streamBuilder.build().toList()); @@ -360,39 +369,6 @@ public class EasyStreamTest { Assert.assertEquals(ListUtil.of((Object) null), EasyStream.of((Object) null).reverse().toList()); } - @Test - public void testZip() { - final List orders = Arrays.asList(1, 2, 3); - final List list = Arrays.asList("dromara", "hutool", "sweet"); - List zip = EasyStream.of(orders).zip(list, (e1, e2) -> e1 + "." + e2).toList(); - Assert.assertEquals(Arrays.asList("1.dromara", "2.hutool", "3.sweet"), zip); - - zip = EasyStream.iterate(1, i -> i + 1).zip(list, (e1, e2) -> e1 + "." + e2).toList(); - Assert.assertEquals(Arrays.asList("1.dromara", "2.hutool", "3.sweet"), zip); - } - - @Test - public void testListSplit() { - final List list = Arrays.asList(1, 2, 3, 4, 5); - List> lists = EasyStream.of(list).split(2).map(EasyStream::toList).toList(); - Assert.assertEquals(ListUtil.split(list, 2), lists); - - // 指定长度 大于等于 列表长度 - lists = EasyStream.of(list).split(list.size()).map(EasyStream::toList).toList(); - Assert.assertEquals(singletonList(list), lists); - } - - @Test - public void testSplitList() { - final List list = Arrays.asList(1, 2, 3, 4, 5); - List> lists = EasyStream.of(list).splitList(2).toList(); - Assert.assertEquals(ListUtil.split(list, 2), lists); - - // 指定长度 大于等于 列表长度 - lists = EasyStream.of(list).splitList(list.size()).toList(); - Assert.assertEquals(singletonList(list), lists); - } - @Test public void testTakeWhile() { // 1 到 10 @@ -557,7 +533,7 @@ public class EasyStreamTest { } @Data - @Builder + @lombok.Builder public static class Student { private String name; private Integer age; 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 index 7e91c0144..532460166 100644 --- a/hutool-core/src/test/java/cn/hutool/core/stream/EntryStreamTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/stream/EntryStreamTest.java @@ -31,6 +31,17 @@ public class EntryStreamTest { EntryStream.merge(Arrays.asList(1, 2), Arrays.asList(1, 2, 3)) .collectKeys(Collectors.toList()) ); + + Assert.assertEquals( + Arrays.asList(1, 2), + EntryStream.merge(null, Arrays.asList(1, 2)) + .collectValues(Collectors.toList()) + ); + Assert.assertEquals( + Arrays.asList(1, 2), + EntryStream.merge(Arrays.asList(1, 2), null) + .collectKeys(Collectors.toList()) + ); } @Test @@ -38,15 +49,21 @@ public class EntryStreamTest { Map map = new HashMap<>(); map.put("1", "1"); Assert.assertEquals(1, EntryStream.of(map).count()); + Assert.assertEquals(0, EntryStream.of((Map)null).count()); Set> entries = new HashSet<>(); entries.add(new Entry<>(1, 1)); entries.add(null); Assert.assertEquals(2, EntryStream.of(entries).count()); + Assert.assertEquals(0, EntryStream.of((Set>)null).count()); Assert.assertEquals(2, EntryStream.of(entries.stream()).count()); + Assert.assertEquals(0, EntryStream.of((Stream>)null).count()); + Assert.assertEquals(2, new EntryStream<>(entries.stream()).count()); + Assert.assertThrows(NullPointerException.class, () -> new EntryStream<>(null)); Iterable iterable = Arrays.asList(1, 2, null); Assert.assertEquals(3, EntryStream.of(iterable, Function.identity(), Function.identity()).count()); + Assert.assertEquals(0, EntryStream.of(null, Function.identity(), Function.identity()).count()); } @Test @@ -117,11 +134,65 @@ public class EntryStreamTest { Assert.assertEquals( 5, EntryStream.of(Arrays.asList(1, 2, 3), Function.identity(), Function.identity()) - .append(4, 4) - .append(5, 5) + .push(4, 4) + .push(5, 5) .count() ); + } + @Test + public void testUnshift() { + Assert.assertEquals( + 5, + EntryStream.of(Arrays.asList(1, 2, 3), Function.identity(), Function.identity()) + .unshift(4, 4) + .unshift(5, 5) + .count() + ); + } + + @Test + public void testAppend() { + Map map1 = new HashMap(){{ + put(1, 1); + put(2, 2); + }}; + Map map2 = new HashMap(){{ + put(3, 3); + put(4, 4); + }}; + Assert.assertEquals( + new ArrayList>(){{ + addAll(map1.entrySet()); + addAll(map2.entrySet()); + }}, + EntryStream.of(map1).append(map2.entrySet()).toList() + ); + Assert.assertEquals( + new ArrayList<>(map1.entrySet()), EntryStream.of(map1).append(null).toList() + ); + } + + @Test + public void testPrepend() { + Map map1 = new HashMap(){{ + put(1, 1); + put(2, 2); + }}; + Map map2 = new HashMap(){{ + put(3, 3); + put(4, 4); + }}; + Assert.assertEquals( + new ArrayList>(){{ + addAll(map2.entrySet()); + addAll(map1.entrySet()); + }}, + EntryStream.of(map1).prepend(map2.entrySet()).toList() + ); + Assert.assertEquals( + new ArrayList<>(map1.entrySet()), EntryStream.of(map1).prepend(null).toList() + ); } @Test @@ -230,6 +301,12 @@ public class EntryStreamTest { .map((k, v) -> k.toString() + v.toString()) .collect(Collectors.toList()) ); + Assert.assertEquals( + Arrays.asList("11", "22", "33"), + EntryStream.of(map) + .map(e -> e.getKey().toString() + e.getValue().toString()) + .collect(Collectors.toList()) + ); } @Test @@ -457,7 +534,7 @@ public class EntryStreamTest { Map map = new HashMap<>(); map.put(1, null); map.put(null, 1); - Assert.assertEquals(0, EntryStream.of(map).nonNull().count()); + Assert.assertEquals(0, EntryStream.of(map).nonNullKeyValue().count()); } @Test @@ -465,7 +542,7 @@ public class EntryStreamTest { Map map = new HashMap<>(); map.put(1, null); map.put(null, 1); - Assert.assertEquals(1, EntryStream.of(map).keyNonNull().count()); + Assert.assertEquals(1, EntryStream.of(map).nonNullKey().count()); } @Test @@ -473,7 +550,7 @@ public class EntryStreamTest { Map map = new HashMap<>(); map.put(1, null); map.put(null, 1); - Assert.assertEquals(1, EntryStream.of(map).valueNonNull().count()); + Assert.assertEquals(1, EntryStream.of(map).nonNullValue().count()); } private static class Entry implements Map.Entry {