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..25e4bc2b3 --- /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 unwrap() { + return stream; + } + + /** + * 创建一个流包装器 + * + * @param stream 包装的流对象 + * @throws NullPointerException 当{@code unwrap}为{@code null}时抛出 + */ + protected AbstractEnhancedWrappedStream(final Stream stream) { + this.stream = Objects.requireNonNull(stream, "unwrap must not null"); + } + + /** + * 获取当前被包装的实例的哈希值 + * + * @return 哈希值 + */ + @Override + public int hashCode() { + return stream.hashCode(); + } + + /** + * 比较被包装的实例是否相等 + * + * @param obj 对象 + * @return 是否相等 + */ + @Override + public boolean equals(final 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 5bceb52df..94a55651f 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 { @@ -100,7 +102,7 @@ public class CollectorUtil { final K key = Opt.ofNullable(t).map(classifier).orElse(null); final A container = m.computeIfAbsent(key, k -> downstreamSupplier.get()); if (ArrayUtil.isArray(container) || Objects.nonNull(t)) { - // 如果是数组类型,不需要判空,场景——分组后需要使用:java.util.stream.Collectors.counting 求null元素个数 + // 如果是数组类型,不需要判空,场景——分组后需要使用:java.util.unwrap.Collectors.counting 求null元素个数 downstreamAccumulator.accept(container, t); } }; @@ -268,6 +270,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} * @@ -277,6 +293,7 @@ public class CollectorUtil { * @param 元素的键类型 * @param 元素的值类型 * @return 收集器 + * @since 6.0.0 */ public static Collector, EntryStream> toEntryStream( final Function keyMapper, final Function valueMapper) { @@ -290,6 +307,7 @@ public class CollectorUtil { * * @param 输入元素类型 * @return 收集器 + * @since 6.0.0 */ public static Collector> toEasyStream() { return transform(ArrayList::new, EasyStream::of); @@ -300,7 +318,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);
 	 * }
* @@ -310,6 +328,7 @@ public class CollectorUtil { * @param 输入元素类型 * @param 中间收集输入元素的集合类型 * @return 收集器 + * @since 6.0.0 */ public static > Collector transform( final Supplier collFactory, final Function mapper) { @@ -336,6 +355,7 @@ public class CollectorUtil { * @param 返回值类型 * @param 输入元素类型 * @return 收集器 + * @since 6.0.0 */ public static Collector, R> transform(final 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 e32d06d69..0d5b22f14 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,29 +1,21 @@ 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.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.text.StrUtil; 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.concurrent.atomic.AtomicInteger; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Spliterator; import java.util.function.*; -import java.util.stream.Collector; -import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; /** - *

{@link Stream}的扩展实现,基于原生Stream进行了封装和增强。
- * 作者经对比了vavr、eclipse-collection、stream-ex以及其他语言的api,结合日常使用习惯,进行封装和拓展 + *

单元素的扩展流实现。基于原生Stream进行了封装和增强。
+ * 作者经对比了vavr、eclipse-collection、unwrap-ex以及其他语言的api,结合日常使用习惯,进行封装和拓展 * Stream为集合提供了一些易用api,它让开发人员能使用声明式编程的方式去编写代码。 * *

中间操作和结束操作

@@ -53,14 +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 StreamWrapper> implements Stream, Iterable { - /** - * 代表不存在的下标, 一般用于并行流的下标, 或者未找到元素时的下标 - */ - private static final int NOT_FOUND_INDEX = -1; +public class EasyStream extends AbstractEnhancedWrappedStream> { /** * 构造 @@ -78,12 +67,12 @@ public class EasyStream extends StreamWrapper> implements St * 返回{@code FastStream}的建造器 * * @param 元素的类型 - * @return a stream builder + * @return a unwrap 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) { @@ -252,46 +241,6 @@ public class EasyStream extends StreamWrapper> implements St // --------------------------------------------------------------- Static method end // endregion - /** - * 过滤元素,返回与 指定操作结果 匹配 指定值 的元素组成的流 - * 这是一个无状态中间操作 - * - * @param 返回类型 - * @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)); - } - - /** - * 过滤元素,返回与指定断言匹配的元素组成的流,断言带下标,并行流时下标永远为-1 - * 这是一个无状态中间操作 - * - * @param predicate 断言 - * @return 返回叠加过滤操作后的流 - */ - public EasyStream 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 过滤后的流 - */ - public EasyStream nonNull() { - return new EasyStream<>(stream.filter(Objects::nonNull)); - } - /** * 返回与指定函数将元素作为参数执行的结果组成的流 * 这是一个无状态中间操作 @@ -302,386 +251,10 @@ public class EasyStream extends StreamWrapper> implements St */ @Override public 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)))); - } - } - - /** - * 返回与指定函数将元素作为参数执行后组成的流。操作带下标,并行流时下标永远为-1 - * 这是一个无状态中间操作 - * - * @param action 指定的函数 - * @return 返回叠加操作后的FastStream - * @apiNote 该方法存在的意义主要是用来调试 - * 当你需要查看经过操作管道某处的元素和下标,可以执行以下操作: - *
{@code
-	 *     .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());
-	 * }
- */ - public EasyStream peekIdx(final BiConsumer action) { - Objects.requireNonNull(action); - if (isParallel()) { - return peek(e -> action.accept(e, NOT_FOUND_INDEX)); - } else { - final AtomicInteger index = new AtomicInteger(NOT_FOUND_INDEX); - return peek(e -> action.accept(e, index.incrementAndGet())); - } - } - - /** - * 返回叠加调用{@link Console#log(Object)}打印出结果的流 - * - * @return 返回叠加操作后的FastStream - */ - public EasyStream log() { - return peek(Console::log); - } - - /** - * 对流里面的每一个元素执行一个操作,操作带下标,并行流时下标永远为-1 - * 这是一个终端操作 - * - * @param action 操作 - */ - public 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())); - } - } - - /** - * 对流里面的每一个元素按照顺序执行一个操作,操作带下标,并行流时下标永远为-1 - * 这是一个终端操作 - * - * @param action 操作 - */ - public 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())); - } - } - - /** - * 获取与给定断言匹配的第一个元素 - * - * @param predicate 断言 - * @return 与给定断言匹配的第一个元素 - */ - public Optional findFirst(final Predicate predicate) { - return stream.filter(predicate).findFirst(); - } - - /** - * 获取与给定断言匹配的第一个元素的下标,并行流下标永远为-1 - * - * @param predicate 断言 - * @return 与给定断言匹配的第一个元素的下标,如果不存在则返回-1 - */ - public int findFirstIdx(final Predicate predicate) { - Objects.requireNonNull(predicate); - if (isParallel()) { - return NOT_FOUND_INDEX; - } else { - final MutableInt index = new MutableInt(NOT_FOUND_INDEX); - //noinspection ResultOfMethodCallIgnored - stream.filter(e -> { - index.increment(); - return predicate.test(e); - }).findFirst(); - return index.get(); - } - } - - /** - * 获取最后一个元素 - * - * @return 最后一个元素 - */ - public Optional findLast() { - final MutableObj last = new MutableObj<>(null); - spliterator().forEachRemaining(last::set); - return Optional.ofNullable(last.get()); - } - - /** - * 获取与给定断言匹配的最后一个元素 - * - * @param predicate 断言 - * @return 与给定断言匹配的最后一个元素 - */ - public Optional findLast(final Predicate predicate) { - Objects.requireNonNull(predicate); - final MutableObj last = new MutableObj<>(null); - spliterator().forEachRemaining(e -> { - if (predicate.test(e)) { - last.set(e); - } - }); - return Optional.ofNullable(last.get()); - } - - /** - * 获取与给定断言匹配的最后一个元素的下标,并行流下标永远为-1 - * - * @param predicate 断言 - * @return 与给定断言匹配的最后一个元素的下标,如果不存在则返回-1 - */ - public int findLastIdx(final Predicate predicate) { - Objects.requireNonNull(predicate); - if (isParallel()) { - return NOT_FOUND_INDEX; - } else { - final MutableInt idxRef = new MutableInt(NOT_FOUND_INDEX); - forEachIdx((e, i) -> { - if (predicate.test(e)) { - idxRef.set(i); - } - }); - return idxRef.get(); - } - } - - /** - * 反转顺序 - * - * @return 反转元素顺序 - */ - @SuppressWarnings("unchecked") - public EasyStream reverse() { - final T[] array = (T[]) toArray(); - ArrayUtil.reverse(array); - return of(array).parallel(isParallel()).onClose(stream::close); - } - - /** - * 更改流的并行状态 - * - * @param parallel 是否并行 - * @return 流 - */ - public EasyStream parallel(final boolean parallel) { - return parallel ? parallel() : sequential(); - } - - /** - * 与给定元素组成的流合并,成为新的流 - * - * @param obj 元素 - * @return 流 - */ - public EasyStream push(final T obj) { - return EasyStream.concat(this.stream, of(obj)); - } - - /** - * 与给定元素组成的流合并,成为新的流 - * - * @param obj 元素 - * @return 流 - */ - @SuppressWarnings("unchecked") - 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); - } - - /** - * 给定元素组成的流与当前流合并,成为新的流 - * - * @param obj 元素 - * @return 流 - */ - @SafeVarargs - public final EasyStream unshift(final T... obj) { - return EasyStream.concat(of(obj), this.stream); - } - - /** - * 获取流中指定下标的元素,如果是负数,则从最后一个开始数起 - * - * @param idx 下标 - * @return 指定下标的元素 - */ - @SuppressWarnings("unchecked") - public Optional at(final Integer idx) { - return Opt.ofNullable(idx).map(i -> (T) ArrayUtil.get(toArray(), i)).toOptional(); - } - /** * 根据一个原始的流,返回一个新包装类实例 * @@ -689,173 +262,29 @@ public class EasyStream extends StreamWrapper> implements St * @return 实现类 */ @Override - protected EasyStream convertToStreamImpl(final Stream stream) { + public EasyStream wrap(final Stream stream) { return new EasyStream<>(stream); } /** - * 转换成集合 - * - * @param collectionFactory 集合工厂(可以是集合构造器) - * @param 集合类型 - * @return 集合 - */ - public > C toColl(final Supplier collectionFactory) { - return collect(Collectors.toCollection(collectionFactory)); - } - - /** - * 转换为ArrayList - * - * @return list - */ - public List toList() { - return collect(Collectors.toList()); - } - - /** - * 转换为HashSet - * - * @return hashSet - */ - public Set toSet() { - return collect(Collectors.toSet()); - } - - /** - * 与给定的可迭代对象转换成Map,key为现有元素,value为给定可迭代对象迭代的元素
- * Map的大小与两个集合中较小的数量一致, 即, 只合并下标位置相同的部分 - * - * @param other 可迭代对象 - * @param 可迭代对象迭代的元素类型 - * @return map,key为现有元素,value为给定可迭代对象迭代的元素 - */ - public Map toZip(final Iterable other) { - final Spliterator keys = spliterator(); - final Spliterator values = Opt.ofNullable(other).map(Iterable::spliterator).orElseGet(Spliterators::emptySpliterator); - // 获取两个Spliterator的中较小的数量 - // 如果Spliterator经过流操作, getExactSizeIfKnown()可能会返回-1, 所以默认大小为 MapUtil.DEFAULT_INITIAL_CAPACITY - final int sizeIfKnown = (int) Math.max(Math.min(keys.getExactSizeIfKnown(), values.getExactSizeIfKnown()), MapUtil.DEFAULT_INITIAL_CAPACITY); - final Map map = MapUtil.newHashMap(sizeIfKnown); - // 保存第一个Spliterator的值 - final MutableObj key = new MutableObj<>(); - // 保存第二个Spliterator的值 - final MutableObj value = new MutableObj<>(); - // 当两个Spliterator中都还有剩余元素时 - while (keys.tryAdvance(key::set) && values.tryAdvance(value::set)) { - map.put(key.get(), value.get()); - } - return map; - } - - /** - * 返回拼接后的字符串 - * - * @return 拼接后的字符串 - */ - public String join() { - return join(StrUtil.EMPTY); - } - - /** - * 返回拼接后的字符串 - * - * @param delimiter 分隔符 - * @return 拼接后的字符串 - */ - public String join(final CharSequence delimiter) { - return join(delimiter, StrUtil.EMPTY, StrUtil.EMPTY); - } - - /** - * 返回拼接后的字符串 - * - * @param delimiter 分隔符 - * @param prefix 前缀 - * @param suffix 后缀 - * @return 拼接后的字符串 - */ - public String join(final CharSequence delimiter, - final CharSequence prefix, - final CharSequence suffix) { - return map(String::valueOf).collect(CollectorUtil.joining(delimiter, prefix, suffix, Function.identity())); - } - - /** - * 转换为map,key为给定操作执行后的返回值,value为当前元素 - * - * @param keyMapper 指定的key操作 - * @param key类型 - * @return map - */ - public Map toMap(final Function keyMapper) { - return toMap(keyMapper, Function.identity()); - } - - /** - * 转换为map,key,value为给定操作执行后的返回值 - * - * @param keyMapper 指定的key操作 - * @param valueMapper 指定value操作 - * @param key类型 - * @param value类型 - * @return map - */ - public Map toMap(final Function keyMapper, - final Function valueMapper) { - return toMap(keyMapper, valueMapper, (l, r) -> r); - } - - /** - * 转换为map,key,value为给定操作执行后的返回值 - * - * @param keyMapper 指定的key操作 - * @param valueMapper 指定value操作 - * @param mergeFunction 合并操作 - * @param key类型 - * @param value类型 - * @return map - */ - public Map toMap(final Function keyMapper, - final Function valueMapper, - final BinaryOperator mergeFunction) { - return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new); - } - - /** - * 转换为map,key,value为给定操作执行后的返回值 - * - * @param keyMapper 指定的key操作 - * @param valueMapper 指定value操作 - * @param mergeFunction 合并操作 - * @param mapSupplier map工厂 - * @param key类型 - * @param value类型 - * @param map类型 - * @return map - */ - public > M toMap(final Function keyMapper, - final Function valueMapper, - final BinaryOperator mergeFunction, - final Supplier mapSupplier) { - return collect(CollectorUtil.toMap(keyMapper, valueMapper, mergeFunction, mapSupplier)); - } - - /** - * 将集合转换为树,默认用 {@code parentId == null} 作为顶部,内置一个小递归 - * 因为需要在当前传入数据里查找,所以这是一个结束操作 + *

将集合转换为树,默认用 {@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(final Function idGetter, - final Function pIdGetter, - final BiConsumer> childrenSetter) { + public > List toTree( + final Function idGetter, + final Function pIdGetter, + final BiConsumer> childrenSetter) { final Map> pIdValuesMap = group(pIdGetter); return getChildrenFromMapByPidAndSet(idGetter, childrenSetter, pIdValuesMap, pIdValuesMap.get(null)); } @@ -869,15 +298,20 @@ public class EasyStream extends StreamWrapper> implements St * @param childrenSetter children的setter对应的lambda,可以写作 {@code Student::setChildren} * @param parentPredicate 树顶部的判断条件,可以写作 {@code s -> 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(final Function idGetter, - final Function pIdGetter, - final BiConsumer> childrenSetter, - final Predicate parentPredicate) { + public > List toTree( + final Function idGetter, + final Function pIdGetter, + final BiConsumer> childrenSetter, + final Predicate parentPredicate) { + Objects.requireNonNull(parentPredicate); final List list = toList(); final List parents = EasyStream.of(list).filter(e -> // 此处是为了适配 parentPredicate.test空指针 情况 @@ -898,13 +332,17 @@ public class EasyStream extends StreamWrapper> implements St * @param 此处是id的泛型限制 * @return list 组装好的树 */ - private > List getChildrenFromMapByPidAndSet(final Function idGetter, - final BiConsumer> childrenSetter, - final Map> pIdValuesMap, - final List parents) { + private > List getChildrenFromMapByPidAndSet( + final Function idGetter, + final BiConsumer> childrenSetter, + final Map> pIdValuesMap, + final List parents) { + Objects.requireNonNull(idGetter); + Objects.requireNonNull(childrenSetter); + Objects.requireNonNull(pIdValuesMap); final MutableObj>> recursiveRef = new MutableObj<>(); final Consumer> recursive = values -> EasyStream.of(values, isParallel()).forEach(value -> { - final List children = pIdValuesMap.get(idGetter.apply(value)); + List children = pIdValuesMap.get(idGetter.apply(value)); childrenSetter.accept(value, children); recursiveRef.get().accept(children); }); @@ -914,231 +352,14 @@ public class EasyStream extends StreamWrapper> implements St } /** - * 将树递归扁平化为集合,内置一个小递归(没错,lambda可以写递归) - * 这是一个无状态中间操作 + * 建造者 * - * @param childrenGetter 获取子节点的lambda,可以写作 {@code Student::getChildren} - * @param childrenSetter 设置子节点的lambda,可以写作 {@code Student::setChildren} - * @return EasyStream 一个流 - * eg: - * {@code List students = EasyStream.of(studentTree).flatTree(Student::getChildren, Student::setChildren).toList() } + * @author VampireAchao */ - public EasyStream flatTree(final Function> childrenGetter, final BiConsumer> childrenSetter) { - final MutableObj>> recursiveRef = new MutableObj<>(); - final 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)); - } - - - /** - * 通过给定分组依据进行分组 - * - * @param classifier 分组依据 - * @param 实体中的分组依据对应类型,也是Map中key的类型 - * @return {@link Collector} - */ - public Map> group(final Function classifier) { - return group(classifier, Collectors.toList()); - } - - /** - * 通过给定分组依据进行分组 - * - * @param classifier 分组依据 - * @param downstream 下游操作 - * @param 实体中的分组依据对应类型,也是Map中key的类型 - * @param 下游操作对应返回类型,也是Map中value的类型 - * @param 下游操作在进行中间操作时对应类型 - * @return {@link Collector} - */ - public Map group(final Function classifier, - final Collector downstream) { - return group(classifier, HashMap::new, downstream); - } - - /** - * 通过给定分组依据进行分组 - * - * @param classifier 分组依据 - * @param mapFactory 提供的map - * @param downstream 下游操作 - * @param 实体中的分组依据对应类型,也是Map中key的类型 - * @param 下游操作对应返回类型,也是Map中value的类型 - * @param 下游操作在进行中间操作时对应类型 - * @param 最后返回结果Map类型 - * @return {@link Collector} - */ - public > M group(final Function classifier, - final Supplier mapFactory, - final Collector downstream) { - return collect(CollectorUtil.groupingBy(classifier, mapFactory, downstream)); - } - - /** - * 将 现有元素 与 给定迭代器中对应位置的元素 使用 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); - } - - /** - * 类似js的
splice函数 - * - * @param start 起始下标 - * @param deleteCount 删除个数,正整数 - * @param items 放入值 - * @return 操作后的流 - */ - @SafeVarargs - public final EasyStream splice(final int start, final int deleteCount, final T... items) { - return of(ListUtil.splice(toList(), start, deleteCount, items)) - .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); - } - - /** - * 保留 与指定断言 匹配时的元素, 在第一次不匹配时终止, 抛弃当前(第一个不匹配元素)及后续所有元素 - *

与 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 与指定断言匹配的元素组成的流 - */ - public EasyStream takeWhile(final Predicate predicate) { - Objects.requireNonNull(predicate); - return of(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 剩余元素组成的流 - */ - public EasyStream dropWhile(final Predicate predicate) { - Objects.requireNonNull(predicate); - return of(StreamUtil.dropWhile(stream, predicate)); - } - - /** - * 流是否为空 - * - * @return 流是否为空 - */ - public boolean isEmpty() { - return !findAny().isPresent(); - } - - /** - * 流是否不为空 - * - * @return 流是否不为空 - */ - public boolean isNotEmpty() { - return !isEmpty(); - } - - /** - * 将当前流转为另一对象。用于提供针对流本身而非流中元素的操作 - * - * @param 转换类型 - * @param transform 转换 - * @return 转换后的流 - */ - public Optional transform(final Function, R> transform) { - Assert.notNull(transform, "transform must not null"); - return Optional.ofNullable(transform.apply(this)); - } - - 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. + * Adds an element to the unwrap being built. * * @param t the element to add * @return {@code this} builder @@ -1150,7 +371,7 @@ public class EasyStream extends StreamWrapper> implements St * return this; * } */ - 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 d97e74a3f..47c7ba24e 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 @@ -1,6 +1,7 @@ package cn.hutool.core.stream; import cn.hutool.core.collection.ConcurrentHashSet; +import cn.hutool.core.collection.iter.IterUtil; import cn.hutool.core.map.multi.RowKeyTable; import cn.hutool.core.map.multi.Table; import cn.hutool.core.util.ObjUtil; @@ -13,15 +14,21 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; /** - *

针对键值对对象{@link Map.Entry}特化的增强流, - * 本身可视为一个元素类型为{@link Map.Entry}的{@link Stream}。
- * 用于支持流式处理{@link Map}集合中的、或具有潜在可能转为{@link Map}集合的数据。 + *

参考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 StreamWrapper, EntryStream> { +public class EntryStream extends AbstractEnhancedWrappedStream, EntryStream> { + + /** + * 默认的空键值对 + */ + private static final Map.Entry EMPTY_ENTRY = new AbstractMap.SimpleImmutableEntry<>(null, null); /** * 根据键与值的集合创建键值对流,若两集合在相同下标的位置找不到对应的键或值,则使用{@code null}填充。
@@ -31,7 +38,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param values 值集合 * @return {@link EntryStream}实例 */ - public static EntryStream merge(Iterable keys, Iterable values) { + public static EntryStream merge(final Iterable keys, final Iterable values) { final boolean hasKeys = ObjUtil.isNotNull(keys); final boolean hasValues = ObjUtil.isNotNull(values); // 皆为空 @@ -51,7 +58,7 @@ public class EntryStream extends StreamWrapper, EntryStrea final Iterator keyItr = keys.iterator(); final Iterator valueItr = values.iterator(); while (keyItr.hasNext() || valueItr.hasNext()) { - entries.add(new Entry<>( + entries.add(ofEntry( keyItr.hasNext() ? keyItr.next() : null, valueItr.hasNext() ? valueItr.next() : null )); @@ -68,7 +75,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param 值类型 * @return {@link EntryStream}实例 */ - public static EntryStream of(Map map) { + public static EntryStream of(final Map map) { return ObjUtil.isNull(map) ? empty() : of(map.entrySet()); } @@ -83,7 +90,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param 值类型 * @return {@link EntryStream}实例 */ - public static EntryStream of(Iterable> entries) { + public static EntryStream of(final Iterable> entries) { return ObjUtil.isNull(entries) ? empty() : of(StreamSupport.stream(entries.spliterator(), false)); } @@ -99,14 +106,14 @@ public class EntryStream extends StreamWrapper, EntryStrea * @return {@link EntryStream}实例 */ public static EntryStream of( - Iterable source, Function keyMapper, Function valueMapper) { + final Iterable source, final Function keyMapper, final 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))); + .map(t -> ofEntry(keyMapper.apply(t), valueMapper.apply(t))); return new EntryStream<>(stream); } @@ -119,9 +126,9 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param 值类型 * @return {@link EntryStream}实例 */ - public static EntryStream of(Stream> stream) { + public static EntryStream of(final Stream> stream) { return ObjUtil.isNull(stream) ? - empty() : new EntryStream<>(stream.map(Entry::new)); + empty() : new EntryStream<>(stream.map(EntryStream::ofEntry)); } /** @@ -138,23 +145,10 @@ public class EntryStream extends StreamWrapper, EntryStrea /** * 构造 */ - EntryStream(Stream> stream) { + EntryStream(final Stream> stream) { super(stream); } - // ================================ override ================================ - - /** - * 根据一个原始的流,返回一个新包装类实例 - * - * @param stream 流 - * @return 实现类 - */ - @Override - protected EntryStream convertToStreamImpl(Stream> stream) { - return new EntryStream<>(stream); - } - // ================================ 中间操作 ================================ /** @@ -163,9 +157,10 @@ public class EntryStream extends StreamWrapper, EntryStrea * @return {@link EntryStream}实例 */ public EntryStream distinctByKey() { - Set accessed = new ConcurrentHashSet<>(16); - return new EntryStream<>(stream.filter(e -> { - K key = e.getKey(); + // FIXME fix happen NPE when has null key + final Set accessed = new ConcurrentHashSet<>(16); + return wrap(stream.filter(e -> { + final K key = e.getKey(); if (accessed.contains(key)) { return false; } @@ -180,9 +175,10 @@ public class EntryStream extends StreamWrapper, EntryStrea * @return {@link EntryStream}实例 */ public EntryStream distinctByValue() { - Set accessed = new ConcurrentHashSet<>(16); - return new EntryStream<>(stream.filter(e -> { - V val = e.getValue(); + // FIXME fix happen NPE when has null value + final Set accessed = new ConcurrentHashSet<>(16); + return wrap(stream.filter(e -> { + final V val = e.getValue(); if (accessed.contains(val)) { return false; } @@ -197,7 +193,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param filter 判断条件 * @return {@link EntryStream}实例 */ - public EntryStream filter(BiPredicate filter) { + public EntryStream filter(final BiPredicate filter) { Objects.requireNonNull(filter); return super.filter(e -> filter.test(e.getKey(), e.getValue())); } @@ -208,7 +204,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param filter 判断条件 * @return {@link EntryStream}实例 */ - public EntryStream filterByKey(Predicate filter) { + public EntryStream filterByKey(final Predicate filter) { Objects.requireNonNull(filter); return super.filter(e -> filter.test(e.getKey())); } @@ -219,7 +215,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param filter 判断条件 * @return {@link EntryStream}实例 */ - public EntryStream filterByValue(Predicate filter) { + public EntryStream filterByValue(final Predicate filter) { Objects.requireNonNull(filter); return super.filter(e -> filter.test(e.getValue())); } @@ -229,7 +225,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * * @return {@link EntryStream}实例 */ - public EntryStream nonNull() { + public EntryStream nonNullKeyValue() { return super.filter(e -> ObjUtil.isNotNull(e) && ObjUtil.isNotNull(e.getKey()) && ObjUtil.isNotNull(e.getValue())); } @@ -238,7 +234,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * * @return {@link EntryStream}实例 */ - public EntryStream keyNonNull() { + public EntryStream nonNullKey() { return super.filter(e -> ObjUtil.isNotNull(e) && ObjUtil.isNotNull(e.getKey())); } @@ -247,7 +243,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * * @return {@link EntryStream}实例 */ - public EntryStream valueNonNull() { + public EntryStream nonNullValue() { return super.filter(e -> ObjUtil.isNotNull(e) && ObjUtil.isNotNull(e.getValue())); } @@ -257,7 +253,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param consumer 操作 * @return {@link EntryStream}实例 */ - public EntryStream peekKey(Consumer consumer) { + public EntryStream peekKey(final Consumer consumer) { Objects.requireNonNull(consumer); return super.peek(e -> consumer.accept(e.getKey())); } @@ -268,7 +264,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param consumer 操作 * @return {@link EntryStream}实例 */ - public EntryStream peekValue(Consumer consumer) { + public EntryStream peekValue(final Consumer consumer) { Objects.requireNonNull(consumer); return super.peek(e -> consumer.accept(e.getValue())); } @@ -279,7 +275,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param comparator 排序器 * @return {@link EntryStream}实例 */ - public EntryStream sortByKey(Comparator comparator) { + public EntryStream sortByKey(final Comparator comparator) { Objects.requireNonNull(comparator); return sorted(Map.Entry.comparingByKey(comparator)); } @@ -290,7 +286,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param comparator 排序器 * @return {@link EntryStream}实例 */ - public EntryStream sortByValue(Comparator comparator) { + public EntryStream sortByValue(final Comparator comparator) { Objects.requireNonNull(comparator); return sorted(Map.Entry.comparingByValue(comparator)); } @@ -304,8 +300,51 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param value 值 * @return {@link EntryStream}实例 */ - public EntryStream push(K key, V value) { - return new EntryStream<>(Stream.concat(stream, Stream.of(new Entry<>(key, value)))); + public EntryStream push(final K key, final V value) { + return wrap(Stream.concat(stream, Stream.of(ofEntry(key, value)))); + } + + /** + * 项当前流队首追加元素 + * + * @param key 键 + * @param value 值 + * @return {@link EntryStream}实例 + */ + public EntryStream unshift(final K key, final V value) { + return wrap(Stream.concat(Stream.of(ofEntry(key, value)), stream)); + } + + /** + * 将输入元素转为流,返回一个前半段为当前流,后半段为新流的新{@link EasyStream}实例 + * + * @param entries 键值对 + * @return {@link EntryStream}实例 + */ + @Override + public EntryStream append(final Iterable> entries) { + if (IterUtil.isEmpty(entries)) { + return this; + } + final Stream> contacted = StreamSupport.stream(entries.spliterator(), isParallel()) + .map(EntryStream::ofEntry); + return wrap(Stream.concat(stream, contacted)); + } + + /** + * 将输入元素转为流,返回一个前半段为新流,后半段为当前流的新{@link EasyStream}实例 + * + * @param entries 键值对 + * @return {@link EntryStream}实例 + */ + @Override + public EntryStream prepend(final Iterable> entries) { + if (IterUtil.isEmpty(entries)) { + return this; + } + final Stream> contacted = StreamSupport.stream(entries.spliterator(), isParallel()) + .map(EntryStream::ofEntry); + return wrap(Stream.concat(contacted, stream)); } /** @@ -333,10 +372,10 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param 新的键类型 * @return {@link EntryStream}实例 */ - public EntryStream mapKeys(Function mapper) { + public EntryStream mapKeys(final Function mapper) { Objects.requireNonNull(mapper); return new EntryStream<>( - stream.map(e -> new Entry<>(mapper.apply(e.getKey()), e.getValue())) + stream.map(e -> ofEntry(mapper.apply(e.getKey()), e.getValue())) ); } @@ -347,10 +386,10 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param 新的值类型 * @return {@link EntryStream}实例 */ - public EntryStream mapValues(Function mapper) { + public EntryStream mapValues(final Function mapper) { Objects.requireNonNull(mapper); return new EntryStream<>( - stream.map(e -> new Entry<>(e.getKey(), mapper.apply(e.getValue()))) + stream.map(e -> ofEntry(e.getKey(), mapper.apply(e.getValue()))) ); } @@ -363,7 +402,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @return 返回叠加操作后的流 */ @Override - public EasyStream map(Function, ? extends R> mapper) { + public EasyStream map(final Function, ? extends R> mapper) { Objects.requireNonNull(mapper); return EasyStream.of(stream.map(mapper)); } @@ -375,7 +414,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param 函数执行后返回流中元素的类型 * @return 映射后的单对象组成的流 */ - public EasyStream map(BiFunction mapper) { + public EasyStream map(final BiFunction mapper) { Objects.requireNonNull(mapper); return EasyStream.of(stream.map(e -> mapper.apply(e.getKey(), e.getValue()))); } @@ -393,7 +432,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @return 返回叠加拆分操作后的流 */ @Override - public EasyStream flatMap(Function, ? extends Stream> mapper) { + public EasyStream flatMap(final Function, ? extends Stream> mapper) { Objects.requireNonNull(mapper); return EasyStream.of(stream.flatMap(mapper)); } @@ -403,21 +442,21 @@ public class EntryStream extends StreamWrapper, EntryStrea * 然后再返回由这些流中所有元素组成的流新{@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}]
+	 * // unwrap = [{a = 1}, {b = 2}, {c = 3}]
+	 * unwrap.flatMapKey(key -> Stream.of(key + "1", key + "2"));
+	 * // unwrap = [{a1 = 1}, {a2 = 1}, {b1 = 2}, {b2 = 2}, {c1 = 3}, {c2 = 3}]
 	 * }
* * @param keyMapper 值转映射方法 * @param 新的键类型 * @return 返回叠加拆分操作后的流 */ - public EntryStream flatMapKey(Function> keyMapper) { + public EntryStream flatMapKey(final Function> keyMapper) { Objects.requireNonNull(keyMapper); return new EntryStream<>( stream.flatMap(e -> keyMapper .apply(e.getKey()) - .map(newKey -> new Entry<>(newKey, e.getValue())) + .map(newKey -> ofEntry(newKey, e.getValue())) ) ); } @@ -427,21 +466,21 @@ public class EntryStream extends StreamWrapper, EntryStrea * 然后再返回由这些流中所有元素组成的流新{@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}]
+	 * // unwrap = [{a = 1}, {b = 2}, {c = 3}]
+	 * unwrap.flatMapValue(num -> Stream.of(num, num+1));
+	 * // unwrap = [{a = 1}, {a = 2}, {b = 2}, {b = 3}, {c = 3}, {c = 4}]
 	 * }
* * @param valueMapper 值转映射方法 * @param 新的值类型 * @return 返回叠加拆分操作后的流 */ - public EntryStream flatMapValue(Function> valueMapper) { + public EntryStream flatMapValue(final Function> valueMapper) { Objects.requireNonNull(valueMapper); return new EntryStream<>( stream.flatMap(e -> valueMapper .apply(e.getValue()) - .map(newVal -> new Entry<>(e.getKey(), newVal)) + .map(newVal -> ofEntry(e.getKey(), newVal)) ) ); } @@ -456,7 +495,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @return 集合 * @see Collectors#toMap(Function, Function, BinaryOperator, Supplier) */ - public Map toMap(Supplier> mapFactory, BinaryOperator operator) { + public Map toMap(final Supplier> mapFactory, final BinaryOperator operator) { Objects.requireNonNull(mapFactory); Objects.requireNonNull(operator); return super.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, operator, mapFactory)); @@ -469,7 +508,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @return 集合 * @see Collectors#toMap(Function, Function, BinaryOperator) */ - public Map toMap(Supplier> mapFactory) { + public Map toMap(final Supplier> mapFactory) { Objects.requireNonNull(mapFactory); return super.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (t1, t2) -> t2, mapFactory)); } @@ -496,7 +535,9 @@ public class EntryStream extends StreamWrapper, EntryStrea * @see Collectors#groupingBy(Function, Supplier, Collector) */ public Table toTable( - BiFunction rowKeyMapper, Supplier> colMapFactory, BinaryOperator operator) { + final BiFunction rowKeyMapper, + final Supplier> colMapFactory, + final BinaryOperator operator) { Objects.requireNonNull(rowKeyMapper); Objects.requireNonNull(colMapFactory); Objects.requireNonNull(operator); @@ -516,7 +557,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @return 集合 * @throws IllegalArgumentException 当父集合或子集合中的键重复时抛出 */ - public Table toTable(BiFunction rowKeyMapper) { + public Table toTable(final BiFunction rowKeyMapper) { return toTable(rowKeyMapper, HashMap::new, throwingMerger()); } @@ -530,7 +571,9 @@ public class EntryStream extends StreamWrapper, EntryStrea * @return 集合 */ public Table toTableByKey( - Function rowKeyMapper, Supplier> colMapFactory, BinaryOperator operator) { + final Function rowKeyMapper, + final Supplier> colMapFactory, + final BinaryOperator operator) { return toTable((k, v) -> rowKeyMapper.apply(k), colMapFactory, operator); } @@ -542,7 +585,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @return 集合 * @throws IllegalArgumentException 当父集合或子集合中的键重复时抛出 */ - public Table toTableByKey(Function rowKeyMapper) { + public Table toTableByKey(final Function rowKeyMapper) { return toTable((k, v) -> rowKeyMapper.apply(k)); } @@ -556,7 +599,9 @@ public class EntryStream extends StreamWrapper, EntryStrea * @return 集合 */ public Table toTableByValue( - Function rowKeyMapper, Supplier> colMapFactory, BinaryOperator operator) { + final Function rowKeyMapper, + final Supplier> colMapFactory, + final BinaryOperator operator) { return toTable((k, v) -> rowKeyMapper.apply(v), colMapFactory, operator); } @@ -568,7 +613,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @return 集合 * @throws IllegalArgumentException 当父集合或子集合中的键重复时抛出 */ - public Table toTableByValue(Function rowKeyMapper) { + public Table toTableByValue(final Function rowKeyMapper) { return toTable((k, v) -> rowKeyMapper.apply(v)); } @@ -588,7 +633,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param 值集合的类型 * @return 集合 */ - public > Map groupByKey(Collector collector) { + public > Map groupByKey(final Collector collector) { return groupByKey((Supplier>)HashMap::new, collector); } @@ -601,7 +646,8 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param 返回的map集合类型 * @return 集合 */ - public , M extends Map> M groupByKey(Supplier mapFactory, Collector collector) { + public , M extends Map> M groupByKey( + final Supplier mapFactory, final Collector collector) { return super.collect(Collectors.groupingBy( Map.Entry::getKey, mapFactory, CollectorUtil.transform(ArrayList::new, s -> s.stream().map(Map.Entry::getValue).collect(collector)) @@ -613,7 +659,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * * @param consumer 操作 */ - public void forEach(BiConsumer consumer) { + public void forEach(final BiConsumer consumer) { Objects.requireNonNull(consumer); super.forEach(e -> consumer.accept(e.getKey(), e.getValue())); } @@ -625,7 +671,7 @@ public class EntryStream extends StreamWrapper, EntryStrea */ public EntryStream inverse() { return new EntryStream<>( - stream.map(e -> new Entry<>(e.getValue(), e.getKey())) + stream.map(e -> ofEntry(e.getValue(), e.getKey())) ); } @@ -636,7 +682,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param 返回值类型 * @return 收集容器 */ - public R collectKeys(Collector collector) { + public R collectKeys(final Collector collector) { return toKeyStream().collect(collector); } @@ -647,7 +693,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param 返回值类型 * @return 收集容器 */ - public R collectValues(Collector collector) { + public R collectValues(final Collector collector) { return toValueStream().collect(collector); } @@ -657,7 +703,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param predicate 判断条件 * @return 是否 */ - public boolean anyMatch(BiPredicate predicate) { + public boolean anyMatch(final BiPredicate predicate) { return super.anyMatch(e -> predicate.test(e.getKey(), e.getValue())); } @@ -667,7 +713,7 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param predicate 判断条件 * @return 是否 */ - public boolean allMatch(BiPredicate predicate) { + public boolean allMatch(final BiPredicate predicate) { Objects.requireNonNull(predicate); return super.allMatch(e -> predicate.test(e.getKey(), e.getValue())); } @@ -678,90 +724,39 @@ public class EntryStream extends StreamWrapper, EntryStrea * @param predicate 判断条件 * @return 是否 */ - public boolean noneMatch(BiPredicate predicate) { + public boolean noneMatch(final BiPredicate predicate) { Objects.requireNonNull(predicate); return super.noneMatch(e -> predicate.test(e.getKey(), e.getValue())); } + // ========================= private ========================= + /** - * {@link Map.Entry}的基本实现 + * 将键值对转为{@link AbstractMap.SimpleImmutableEntry} */ - static class Entry implements Map.Entry { + @SuppressWarnings("unchecked") + static Map.Entry ofEntry(final Map.Entry entry) { + return ObjUtil.defaultIfNull( + entry, e -> ofEntry(e.getKey(), e.getValue()), (Map.Entry)EMPTY_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 + '}'; - } + /** + * 将键值对转为{@link AbstractMap.SimpleImmutableEntry} + */ + static Map.Entry ofEntry(final K key, final V value) { + return new AbstractMap.SimpleImmutableEntry<>(key, value); + } + /** + * 根据一个原始的流,返回一个新包装类实例 + * + * @param stream 流 + * @return 实现类 + */ + @Override + public EntryStream wrap(final Stream> stream) { + return new EntryStream<>(stream); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/TerminableWrappedStream.java b/hutool-core/src/main/java/cn/hutool/core/stream/TerminableWrappedStream.java new file mode 100644 index 000000000..d2a09ef7b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/stream/TerminableWrappedStream.java @@ -0,0 +1,513 @@ +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.util.ArrayUtil; + +import java.util.*; +import java.util.function.*; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * {@link WrappedStream}的扩展,用于为实现类提供更多终端操作方法的增强接口, + * 该接口提供的方法,返回值类型都不为{@link Stream}。 + * + * @param 流中的元素类型 + * @param {@link TerminableWrappedStream}的实现类类型 + * @author huangchengxing + * @since 6.0.0 + */ +public interface TerminableWrappedStream> extends WrappedStream { + + // region ============ to collection ============ + + /** + * 转换为{@link ArrayList} + * + * @return 集合 + * @see #toColl(Supplier) + */ + default List toList() { + return this.toColl(ArrayList::new); + } + + /** + * 换为不可变集合 + * + * @return 集合 + * @see #toColl(Supplier) + */ + default List toUnmodifiableList() { + return Collections.unmodifiableList(this.toList()); + } + + /** + * 转换为HashSet + * + * @return 集合 + * @see #toColl(Supplier) + */ + default Set toSet() { + return this.toColl(HashSet::new); + } + + /** + * 换为不可变集合 + * + * @return 集合 + * @see #toColl(Supplier) + */ + default Set toUnmodifiableSet() { + return Collections.unmodifiableSet(this.toSet()); + } + + /** + * 转换成集合 + * + * @param collectionFactory 集合工厂(可以是集合构造器) + * @param 集合类型 + * @return 集合 + */ + default > C toColl(final Supplier collectionFactory) { + Objects.requireNonNull(collectionFactory); + return unwrap().collect(Collectors.toCollection(collectionFactory)); + } + + // endregion + + // region ============ to map ============ + + /** + * 转换为map,key为给定操作执行后的返回值,value为当前元素 + * + * @param keyMapper 指定的key操作 + * @param key类型 + * @return map + * @see #toMap(Function, Function, BinaryOperator, Supplier) + */ + default Map toMap(final Function keyMapper) { + return this.toMap(keyMapper, Function.identity()); + } + + /** + * 转换为map,key,value为给定操作执行后的返回值 + * + * @param keyMapper 指定的key操作 + * @param valueMapper 指定value操作 + * @param key类型 + * @param value类型 + * @return map + * @see #toMap(Function, Function, BinaryOperator, Supplier) + */ + default Map toMap( + final Function keyMapper, final Function valueMapper) { + return this.toMap(keyMapper, valueMapper, (l, r) -> r); + } + + /** + * 转换为不可变map,key,value为给定操作执行后的返回值 + * + * @param keyMapper 指定的key操作 + * @param valueMapper 指定value操作 + * @param key类型 + * @param value类型 + * @return map + * @see #toMap(Function, Function, BinaryOperator, Supplier) + */ + default Map toUnmodifiableMap( + final Function keyMapper, final Function valueMapper) { + return Collections.unmodifiableMap(this.toMap(keyMapper, valueMapper)); + } + + /** + * 转换为map,key,value为给定操作执行后的返回值 + * + * @param keyMapper 指定的key操作 + * @param valueMapper 指定value操作 + * @param mergeFunction 合并操作 + * @param key类型 + * @param value类型 + * @return map + * @see #toMap(Function, Function, BinaryOperator, Supplier) + */ + default Map toMap( + final Function keyMapper, + final Function valueMapper, + final BinaryOperator mergeFunction) { + return this.toMap(keyMapper, valueMapper, mergeFunction, HashMap::new); + } + + /** + * 转换为不可变map,key,value为给定操作执行后的返回值 + * + * @param keyMapper 指定的key操作 + * @param valueMapper 指定value操作 + * @param mergeFunction 合并操作 + * @param key类型 + * @param value类型 + * @return map + * @see #toMap(Function, Function, BinaryOperator, Supplier) + */ + default Map toUnmodifiableMap( + final Function keyMapper, + final Function valueMapper, + final BinaryOperator mergeFunction) { + return Collections.unmodifiableMap( + this.toMap(keyMapper, valueMapper, mergeFunction, HashMap::new) + ); + } + + /** + * 转换为map,key,value为给定操作执行后的返回值 + * + * @param keyMapper 指定的key操作 + * @param valueMapper 指定value操作 + * @param mergeFunction 合并操作 + * @param mapSupplier map工厂 + * @param key类型 + * @param value类型 + * @param map类型 + * @return map + */ + default > M toMap( + final Function keyMapper, + final Function valueMapper, + final BinaryOperator mergeFunction, + Supplier mapSupplier) { + Objects.requireNonNull(keyMapper); + Objects.requireNonNull(valueMapper); + Objects.requireNonNull(mergeFunction); + Objects.requireNonNull(mapSupplier); + return unwrap().collect(Collectors.toMap(keyMapper, valueMapper, mergeFunction, mapSupplier)); + } + + // endregion + + // region ============ to zip ============ + + /** + * 与给定的可迭代对象转换成map,key为现有元素,value为给定可迭代对象迭代的元素
+ * 至少包含全部的key,如果对应位置上的value不存在,则为null + * + * @param other 可迭代对象 + * @param 可迭代对象迭代的元素类型 + * @return map,key为现有元素,value为给定可迭代对象迭代的元素;
+ * 至少包含全部的key,如果对应位置上的value不存在,则为null;
+ * 如果key重复, 则保留最后一个关联的value;
+ */ + default Map toZip(final Iterable other) { + Objects.requireNonNull(other); + // value对象迭代器 + final Iterator iterator = Opt.ofNullable(other).map(Iterable::iterator).orElseGet(Collections::emptyIterator); + if (this.isParallel()) { + final List keyList = toList(); + final Map map = new HashMap<>(keyList.size()); + for (T key : keyList) { + map.put(key, iterator.hasNext() ? iterator.next() : null); + } + return map; + } else { + return this.toMap(Function.identity(), e -> iterator.hasNext() ? iterator.next() : null); + } + } + + // endregion + + // region ============ to optional ============ + + /** + * 将当前流转为另一对象。用于提供针对流本身而非流中元素的操作 + * + * @param 转换类型 + * @param transform 转换 + * @return 转换后的流 + */ + default Optional transform(final Function transform) { + Objects.requireNonNull(transform); + return Optional.ofNullable(transform.apply(wrap(this))); + } + + /** + * 获取与给定断言匹配的第一个元素 + * + * @param predicate 断言 + * @return 与给定断言匹配的第一个元素 + */ + default Optional findFirst(final Predicate predicate) { + Objects.requireNonNull(predicate); + return unwrap().filter(predicate).findFirst(); + } + + /** + * 获取与给定断言匹配的第一个元素的下标,并行流下标永远为-1 + * + * @param predicate 断言 + * @return 与给定断言匹配的第一个元素的下标,如果不存在则返回-1 + */ + default int findFirstIdx(final Predicate predicate) { + Objects.requireNonNull(predicate); + if (isParallel()) { + return NOT_FOUND_ELEMENT_INDEX; + } else { + final MutableInt index = new MutableInt(NOT_FOUND_ELEMENT_INDEX); + unwrap().filter(e -> { + index.increment(); + return predicate.test(e); + }).findFirst(); + return index.get(); + } + } + + /** + * 获取最后一个元素 + * + * @return 最后一个元素 + */ + default Optional findLast() { + final MutableObj last = new MutableObj<>(null); + spliterator().forEachRemaining(last::set); + return Optional.ofNullable(last.get()); + } + + /** + * 获取与给定断言匹配的最后一个元素 + * + * @param predicate 断言 + * @return 与给定断言匹配的最后一个元素 + */ + default Optional findLast(final Predicate predicate) { + Objects.requireNonNull(predicate); + final MutableObj last = new MutableObj<>(null); + spliterator().forEachRemaining(e -> { + if (predicate.test(e)) { + last.set(e); + } + }); + return Optional.ofNullable(last.get()); + } + + /** + * 获取与给定断言匹配的最后一个元素的下标,并行流下标永远为-1 + * + * @param predicate 断言 + * @return 与给定断言匹配的最后一个元素的下标,如果不存在则返回-1 + */ + default int findLastIdx(final Predicate predicate) { + Objects.requireNonNull(predicate); + if (isParallel()) { + return NOT_FOUND_ELEMENT_INDEX; + } else { + final MutableInt idxRef = new MutableInt(NOT_FOUND_ELEMENT_INDEX); + forEachIdx((e, i) -> { + if (predicate.test(e)) { + idxRef.set(i); + } + }); + return idxRef.get(); + } + } + + /** + * 获取流中指定下标的元素,如果是负数,则从最后一个开始数起 + * + * @param idx 下标 + * @return 指定下标的元素 + */ + @SuppressWarnings("unchecked") + default Optional at(final Integer idx) { + return Opt.ofNullable(idx).map(i -> (T) ArrayUtil.get(toArray(), i)).toOptional(); + } + + // endregion + + // region ============ to boolean ============ + + /** + * 流是否为空 + * + * @return 流是否为空 + */ + default boolean isEmpty() { + return !findAny().isPresent(); + } + + /** + * 流是否不为空 + * + * @return 流是否不为空 + */ + default boolean isNotEmpty() { + return !isEmpty(); + } + + // endregion + + // region ============ join ============ + + /** + * 返回拼接后的字符串 + * + * @return 拼接后的字符串 + * @see #join(CharSequence, CharSequence, CharSequence) + */ + default String join() { + return this.join(""); + } + + /** + * 返回拼接后的字符串 + * + * @param delimiter 分隔符 + * @return 拼接后的字符串 + * @see #join(CharSequence, CharSequence, CharSequence) + */ + default String join(final CharSequence delimiter) { + return this.join(delimiter, "", ""); + } + + /** + * 返回拼接后的字符串 + * + * @param delimiter 分隔符 + * @param prefix 前缀 + * @param suffix 后缀 + * @return 拼接后的字符串 + */ + default String join(final CharSequence delimiter, final CharSequence prefix, final CharSequence suffix) { + return unwrap().map(String::valueOf).collect(CollectorUtil.joining(delimiter, prefix, suffix, Function.identity())); + } + + // endregion + + // region ============ group ============ + + /** + * 通过给定分组依据进行分组 + * + * @param classifier 分组依据,得到的键为{@code null}时不会抛出异常 + * @param 实体中的分组依据对应类型,也是Map中key的类型 + * @return map + * @see #group(Function, Supplier, Collector) + */ + default Map> group(final 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( + final Function classifier, final 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( + final Function classifier, + final Supplier mapFactory, + final Collector downstream) { + Objects.requireNonNull(classifier); + Objects.requireNonNull(mapFactory); + Objects.requireNonNull(downstream); + return unwrap().collect(CollectorUtil.groupingBy(classifier, mapFactory, downstream)); + } + + /** + * 根据给定判断条件分组 + * + * @param predicate 判断条件 + * @return map + * @see #partition(Predicate, Collector) + */ + default Map> partition(final Predicate predicate) { + return this.partition(predicate, ArrayList::new); + } + + /** + * 根据给定判断条件分组 + * + * @param predicate 判断条件 + * @param collFactory 提供的集合 + * @return map + * @see #partition(Predicate, Collector) + */ + default > Map partition(final Predicate predicate, final Supplier collFactory) { + return this.partition(predicate, Collectors.toCollection(collFactory)); + } + + /** + * 根据给定判断条件分组 + * + * @param predicate 判断条件 + * @param downstream 下游操作 + * @param 返回值类型 + * @return map + */ + default Map partition(final Predicate predicate, final Collector downstream) { + Objects.requireNonNull(predicate); + Objects.requireNonNull(downstream); + return unwrap().collect(Collectors.partitioningBy(predicate, downstream)); + } + + // endregion + + // region ============ foreach ============ + + /** + * 对流里面的每一个元素执行一个操作,操作带下标,并行流时下标永远为-1 + * 这是一个终端操作 + * + * @param action 操作 + */ + default void forEachIdx(final BiConsumer action) { + Objects.requireNonNull(action); + if (isParallel()) { + unwrap().forEach(e -> action.accept(e, NOT_FOUND_ELEMENT_INDEX)); + } else { + final MutableInt index = new MutableInt(NOT_FOUND_ELEMENT_INDEX); + unwrap().forEach(e -> action.accept(e, index.incrementAndGet())); + } + } + + /** + * 对流里面的每一个元素按照顺序执行一个操作,操作带下标,并行流时下标永远为-1 + * 这是一个终端操作 + * + * @param action 操作 + */ + default void forEachOrderedIdx(final BiConsumer action) { + Objects.requireNonNull(action); + if (isParallel()) { + unwrap().forEachOrdered(e -> action.accept(e, NOT_FOUND_ELEMENT_INDEX)); + } else { + final MutableInt index = new MutableInt(NOT_FOUND_ELEMENT_INDEX); + unwrap().forEachOrdered(e -> action.accept(e, index.incrementAndGet())); + } + } + + // endregion + +} 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..b689db3fa --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/stream/TransformableWrappedStream.java @@ -0,0 +1,571 @@ +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(unwrap()::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(unwrap()::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(final Function keyMapper, final 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(final Function keyMapper) { + return toEntries(keyMapper, Function.identity()); + } + + // region ============ generic ============ + + /** + * 反转顺序 + * + * @return 反转元素顺序 + */ + @SuppressWarnings("unchecked") + default S reverse() { + final T[] array = (T[]) toArray(); + ArrayUtil.reverse(array); + return wrap(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 = unwrap().collect(Collectors.toList()); + return wrap(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 wrap(StreamUtil.takeWhile(unwrap(), 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 wrap(StreamUtil.dropWhile(unwrap(), 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(unwrap().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(unwrap().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(final 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 = unwrap(); + if (ArrayUtil.isNotEmpty(obj)) { + result = Stream.concat(unwrap(), Stream.of(obj)); + } + return wrap(result); + } + + /** + * 给定元素组成的流与当前流合并,成为新的流 + * + * @param obj 元素 + * @return 流 + */ + default S unshift(final T... obj) { + Stream result = unwrap(); + if (ArrayUtil.isNotEmpty(obj)) { + result = Stream.concat(Stream.of(obj), unwrap()); + } + return wrap(result); + } + + /** + * 将输入元素转为流,返回一个前半段为当前流,后半段为新流的新实例 + * + * @param iterable 集合 + * @return {@link EntryStream}实例 + */ + default S append(final Iterable iterable) { + if (IterUtil.isEmpty(iterable)) { + return wrap(this); + } + final Stream contacted = StreamSupport.stream(iterable.spliterator(), isParallel()); + return wrap(Stream.concat(this, contacted)); + } + + /** + * 将输入元素转为流,返回一个前半段为新流,后半段为当前流的新实例 + * + * @param iterable 集合 + * @return {@link EntryStream}实例 + */ + default S prepend(final Iterable iterable) { + if (IterUtil.isEmpty(iterable)) { + return wrap(this); + } + final Stream contacted = StreamSupport.stream(iterable.spliterator(), isParallel()); + return wrap(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<>(unwrap().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(final Function> childrenGetter, final BiConsumer> childrenSetter) { + Objects.requireNonNull(childrenGetter); + Objects.requireNonNull(childrenSetter); + final MutableObj>> recursiveRef = new MutableObj<>(); + final Function> recursive = e -> EasyStream.of(childrenGetter.apply(e)) + .flat(recursiveRef.get()) + .unshift(e); + recursiveRef.set(recursive); + return wrap(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<>(unwrap().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 64% 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 558dd7d42..8f9399f16 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 @@ -5,27 +5,45 @@ import java.util.function.*; import java.util.stream.*; /** - * {@link Stream}的包装类,用于基于一个已有的流实例进行扩展 + *

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

    + *
  • {@link EasyStream}:针对单元素的通用增强流实现;
  • + *
  • {@link EntryStream}:针对键值对类型元素的增强流实现;
  • + *
* + * @param 流中的元素类型 + * @param {@link WrappedStream}的实现类类型 * @author huangchengxing + * @see TerminableWrappedStream + * @see TransformableWrappedStream + * @see AbstractEnhancedWrappedStream * @see EasyStream + * @see EntryStream + * @since 6.0.0 */ -abstract class StreamWrapper> implements Stream, Iterable { +public interface WrappedStream> extends Stream, Iterable { /** - * 原始的流实例 + * 代表不存在的下标, 一般用于并行流的下标, 或者未找到元素时的下标 */ - protected final Stream stream; + int NOT_FOUND_ELEMENT_INDEX = -1; /** - * 创建一个流包装器 + * 获取被当前实例包装的流对象 * - * @param stream 包装的流对象 + * @return 被当前实例包装的流对象 */ - protected StreamWrapper(final Stream stream) { - Objects.requireNonNull(stream, "stream must not null"); - this.stream = stream; - } + Stream unwrap(); + + /** + * 将一个原始流包装为指定类型的增强流
+ * 若{@code source}于当前实例包装的流并不相同,则该增强流与当前实例无关联关系 + * + * @param source 被包装的流 + * @return 包装后的流 + */ + S wrap(final Stream source); /** * 过滤元素,返回与指定断言匹配的元素组成的流 @@ -35,8 +53,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 返回叠加过滤操作后的流 */ @Override - public I filter(final Predicate predicate) { - return convertToStreamImpl(stream.filter(predicate)); + default S filter(final Predicate predicate) { + Objects.requireNonNull(predicate); + return wrap(unwrap().filter(predicate)); } /** @@ -47,8 +66,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 叠加操作后元素类型全为int的流 */ @Override - public IntStream mapToInt(final ToIntFunction mapper) { - return stream.mapToInt(mapper); + default IntStream mapToInt(final ToIntFunction mapper) { + Objects.requireNonNull(mapper); + return unwrap().mapToInt(mapper); } /** @@ -59,8 +79,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 叠加操作后元素类型全为long的流 */ @Override - public LongStream mapToLong(final ToLongFunction mapper) { - return stream.mapToLong(mapper); + default LongStream mapToLong(final ToLongFunction mapper) { + Objects.requireNonNull(mapper); + return unwrap().mapToLong(mapper); } /** @@ -71,8 +92,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 叠加操作后元素类型全为double的流 */ @Override - public DoubleStream mapToDouble(final ToDoubleFunction mapper) { - return stream.mapToDouble(mapper); + default DoubleStream mapToDouble(final ToDoubleFunction mapper) { + Objects.requireNonNull(mapper); + return unwrap().mapToDouble(mapper); } /** @@ -83,8 +105,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 返回叠加拆分操作后的IntStream */ @Override - public IntStream flatMapToInt(final Function mapper) { - return stream.flatMapToInt(mapper); + default IntStream flatMapToInt(final Function mapper) { + Objects.requireNonNull(mapper); + return unwrap().flatMapToInt(mapper); } /** @@ -95,8 +118,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 返回叠加拆分操作后的LongStream */ @Override - public LongStream flatMapToLong(final Function mapper) { - return stream.flatMapToLong(mapper); + default LongStream flatMapToLong(final Function mapper) { + Objects.requireNonNull(mapper); + return unwrap().flatMapToLong(mapper); } /** @@ -107,8 +131,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 返回叠加拆分操作后的DoubleStream */ @Override - public DoubleStream flatMapToDouble(final Function mapper) { - return stream.flatMapToDouble(mapper); + default DoubleStream flatMapToDouble(final Function mapper) { + Objects.requireNonNull(mapper); + return unwrap().flatMapToDouble(mapper); } /** @@ -118,8 +143,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 一个具有去重特征的流 */ @Override - public I distinct() { - return convertToStreamImpl(stream.distinct()); + default S distinct() { + return wrap(unwrap().distinct()); } /** @@ -131,8 +156,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 一个元素按自然顺序排序的流 */ @Override - public I sorted() { - return convertToStreamImpl(stream.sorted()); + default S sorted() { + return wrap(unwrap().sorted()); } /** @@ -145,8 +170,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 一个元素按指定的Comparator排序的流 */ @Override - public I sorted(final Comparator comparator) { - return convertToStreamImpl(stream.sorted(comparator)); + default S sorted(final Comparator comparator) { + Objects.requireNonNull(comparator); + return wrap(unwrap().sorted(comparator)); } /** @@ -167,8 +193,9 @@ abstract class StreamWrapper> implements Stream, Itera * } */ @Override - public I peek(final Consumer action) { - return convertToStreamImpl(stream.peek(action)); + default S peek(final Consumer action) { + Objects.requireNonNull(action); + return wrap(unwrap().peek(action)); } /** @@ -179,8 +206,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 截取后的流 */ @Override - public I limit(final long maxSize) { - return convertToStreamImpl(stream.limit(maxSize)); + default S limit(final long maxSize) { + return wrap(unwrap().limit(maxSize)); } /** @@ -191,8 +218,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 丢弃前面n个元素后的剩余元素组成的流 */ @Override - public I skip(final long n) { - return convertToStreamImpl(stream.skip(n)); + default S skip(final long n) { + return wrap(unwrap().skip(n)); } /** @@ -202,8 +229,9 @@ abstract class StreamWrapper> implements Stream, Itera * @param action 操作 */ @Override - public void forEach(final Consumer action) { - stream.forEach(action); + default void forEach(final Consumer action) { + Objects.requireNonNull(action); + unwrap().forEach(action); } /** @@ -213,8 +241,9 @@ abstract class StreamWrapper> implements Stream, Itera * @param action 操作 */ @Override - public void forEachOrdered(final Consumer action) { - stream.forEachOrdered(action); + default void forEachOrdered(final Consumer action) { + Objects.requireNonNull(action); + unwrap().forEachOrdered(action); } /** @@ -224,8 +253,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 包含此流元素的数组 */ @Override - public Object[] toArray() { - return stream.toArray(); + default Object[] toArray() { + return unwrap().toArray(); } /** @@ -238,9 +267,9 @@ abstract class StreamWrapper> implements Stream, Itera * @throws ArrayStoreException 如果元素转换失败,例如不是该元素类型及其父类,则抛出该异常 */ @Override - public A[] toArray(final IntFunction generator) { - //noinspection SuspiciousToArrayCall - return stream.toArray(generator); + default A[] toArray(final IntFunction generator) { + Objects.requireNonNull(generator); + return unwrap().toArray(generator); } /** @@ -264,8 +293,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 聚合计算后的值 */ @Override - public T reduce(final T identity, final BinaryOperator accumulator) { - return stream.reduce(identity, accumulator); + default T reduce(final T identity, final BinaryOperator accumulator) { + Objects.requireNonNull(accumulator); + return unwrap().reduce(identity, accumulator); } /** @@ -274,7 +304,7 @@ abstract class StreamWrapper> implements Stream, Itera *
{@code
 	 *     boolean foundAny = false;
 	 *     T result = null;
-	 *     for (T element : this stream) {
+	 *     for (T element : this unwrap) {
 	 *         if (!foundAny) {
 	 *             foundAny = true;
 	 *             result = element;
@@ -299,8 +329,9 @@ abstract class StreamWrapper> implements Stream, Itera
 	 * @see #max(Comparator)
 	 */
 	@Override
-	public Optional reduce(final BinaryOperator accumulator) {
-		return stream.reduce(accumulator);
+	default Optional reduce(final BinaryOperator accumulator) {
+		Objects.requireNonNull(accumulator);
+		return unwrap().reduce(accumulator);
 	}
 
 	/**
@@ -316,8 +347,10 @@ abstract class StreamWrapper> implements Stream, Itera
 	 * @see #reduce(Object, BinaryOperator)
 	 */
 	@Override
-	public  U reduce(final U identity, final BiFunction accumulator, final BinaryOperator combiner) {
-		return stream.reduce(identity, accumulator, combiner);
+	default  U reduce(final U identity, final BiFunction accumulator, final BinaryOperator combiner) {
+		Objects.requireNonNull(accumulator);
+		Objects.requireNonNull(combiner);
+		return unwrap().reduce(identity, accumulator, combiner);
 	}
 
 	/**
@@ -334,8 +367,11 @@ abstract class StreamWrapper> implements Stream, Itera
 	 * }
*/ @Override - public R collect(final Supplier supplier, final BiConsumer accumulator, final BiConsumer combiner) { - return stream.collect(supplier, accumulator, combiner); + default R collect(final Supplier supplier, final BiConsumer accumulator, final BiConsumer combiner) { + Objects.requireNonNull(supplier); + Objects.requireNonNull(accumulator); + Objects.requireNonNull(combiner); + return unwrap().collect(supplier, accumulator, combiner); } /** @@ -348,8 +384,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 收集后的容器 */ @Override - public R collect(final Collector collector) { - return stream.collect(collector); + default R collect(final Collector collector) { + Objects.requireNonNull(collector); + return unwrap().collect(collector); } /** @@ -359,8 +396,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 最小值 */ @Override - public Optional min(final Comparator comparator) { - return stream.min(comparator); + default Optional min(final Comparator comparator) { + Objects.requireNonNull(comparator); + return unwrap().min(comparator); } /** @@ -370,8 +408,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 最大值 */ @Override - public Optional max(final Comparator comparator) { - return stream.max(comparator); + default Optional max(final Comparator comparator) { + Objects.requireNonNull(comparator); + return unwrap().max(comparator); } /** @@ -380,8 +419,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 流元素个数 */ @Override - public long count() { - return stream.count(); + default long count() { + return unwrap().count(); } /** @@ -391,8 +430,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 是否有任何一个元素满足给定断言 */ @Override - public boolean anyMatch(final Predicate predicate) { - return stream.anyMatch(predicate); + default boolean anyMatch(final Predicate predicate) { + Objects.requireNonNull(predicate); + return unwrap().anyMatch(predicate); } /** @@ -402,8 +442,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 是否所有元素满足给定断言 */ @Override - public boolean allMatch(final Predicate predicate) { - return stream.allMatch(predicate); + default boolean allMatch(final Predicate predicate) { + Objects.requireNonNull(predicate); + return unwrap().allMatch(predicate); } /** @@ -413,8 +454,9 @@ abstract class StreamWrapper> implements Stream, Itera * @return 是否没有元素满足给定断言 */ @Override - public boolean noneMatch(final Predicate predicate) { - return stream.noneMatch(predicate); + default boolean noneMatch(final Predicate predicate) { + Objects.requireNonNull(predicate); + return unwrap().noneMatch(predicate); } /** @@ -423,8 +465,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 第一个元素 */ @Override - public Optional findFirst() { - return stream.findFirst(); + default Optional findFirst() { + return unwrap().findFirst(); } /** @@ -433,8 +475,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 随便取一个 */ @Override - public Optional findAny() { - return stream.findAny(); + default Optional findAny() { + return unwrap().findAny(); } /** @@ -443,8 +485,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 流的迭代器 */ @Override - public Iterator iterator() { - return stream.iterator(); + default Iterator iterator() { + return unwrap().iterator(); } /** @@ -453,8 +495,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 流的拆分器 */ @Override - public Spliterator spliterator() { - return stream.spliterator(); + default Spliterator spliterator() { + return unwrap().spliterator(); } /** @@ -463,8 +505,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 流的并行状态 */ @Override - public boolean isParallel() { - return stream.isParallel(); + default boolean isParallel() { + return unwrap().isParallel(); } /** @@ -473,8 +515,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 串行流 */ @Override - public I sequential() { - return convertToStreamImpl(stream.sequential()); + default S sequential() { + return wrap(unwrap().sequential()); } /** @@ -483,8 +525,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 并行流 */ @Override - public I parallel() { - return convertToStreamImpl(stream.parallel()); + default S parallel() { + return wrap(unwrap().parallel()); } /** @@ -494,8 +536,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 无序流 */ @Override - public I unordered() { - return convertToStreamImpl(stream.unordered()); + default S unordered() { + return wrap(unwrap().unordered()); } /** @@ -505,8 +547,8 @@ abstract class StreamWrapper> implements Stream, Itera * @return 流 */ @Override - public I onClose(final Runnable closeHandler) { - return convertToStreamImpl(stream.onClose(closeHandler)); + default S onClose(Runnable closeHandler) { + return wrap(unwrap().onClose(closeHandler)); } /** @@ -515,50 +557,33 @@ abstract class StreamWrapper> implements Stream, Itera * @see AutoCloseable#close() */ @Override - public void close() { - stream.close(); + default void close() { + unwrap().close(); } /** - * hashcode + * 获取当前实例的哈希值 * - * @return hashcode + * @return 哈希值 */ @Override - public int hashCode() { - return stream.hashCode(); - } + int hashCode(); /** - * equals + * 比较实例是否相等 * * @param obj 对象 - * @return 结果 + * @return 是否相等 */ @Override - public boolean equals(final Object obj) { - if (obj instanceof Stream) { - return stream.equals(obj); - } - return false; - } + boolean equals(final Object obj); /** - * toString + * 将当前实例转为字符串 * - * @return string + * @return 字符串 */ @Override - public String toString() { - return stream.toString(); - } - - /** - * 根据一个原始的流,返回一个新包装类实例 - * - * @param stream 流 - * @return 实现类 - */ - protected abstract I convertToStreamImpl(Stream stream); + String toString(); } 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..6922d30b5 --- /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() { + final List list = asList(1, 2, 3); + final List toList = wrap(list).toList(); + Assert.assertEquals(list, toList); + } + + @Test + public void testToUnmodifiableList() { + final List list = wrap(1, 2, 3) + .toUnmodifiableList(); + Assert.assertThrows(UnsupportedOperationException.class, () -> list.remove(0)); + } + + @Test + public void testToSet() { + final 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() { + final Set set = wrap(1, 2, 3) + .toUnmodifiableSet(); + Assert.assertThrows(UnsupportedOperationException.class, () -> set.remove(0)); + } + + @Test + public void testToCollection() { + final List list = asList(1, 2, 3); + final List toCollection = wrap(list).map(String::valueOf).toColl(LinkedList::new); + Assert.assertEquals(asList("1", "2", "3"), toCollection); + } + + @Test + public void testToMap() { + final List list = asList(1, 2, 3); + final 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() { + final Map map1 = wrap(1, 2, 3).toUnmodifiableMap(Function.identity(), Function.identity()); + Assert.assertThrows(UnsupportedOperationException.class, () -> map1.remove(1)); + final 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() { + final List orders = asList(1, 2, 3); + final List list = asList("dromara", "hutool", "sweet"); + final Map toZip = wrap(orders).toZip(list); + Assert.assertEquals(new HashMap() {{ + put(1, "dromara"); + put(2, "hutool"); + put(3, "sweet"); + }}, toZip); + } + + @Test + public void testTransform() { + final List list = wrap(1, 2, 3).transform(Wrapper::toList).orElse(null); + Assert.assertEquals(asList(1, 2, 3), list); + } + + @Test + public void testFindFirst() { + final 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() { + final List list = asList(1, 2, 3); + Assert.assertEquals(1, wrap(list).findFirstIdx(t -> (t & 1) == 0)); + } + + @Test + public void testFindLast() { + final List list = asList(1, 2, 3); + Assert.assertEquals((Integer)3, wrap(list).findLast(t -> (t & 1) == 1).orElse(null)); + } + + @Test + public void testFindLastIdx() { + final List list = asList(1, 2, 3); + Assert.assertEquals(1, wrap(list).findLastIdx(t -> (t & 1) == 0)); + } + + @Test + public void testAt() { + final 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() { + final List list = asList(1, 2, 3); + final 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() { + final List list = asList(1, 2, 3); + final 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() { + final List list = asList(1, 2, 3); + final Map> map = new HashMap>() {{ + put(Boolean.TRUE, singletonList(2)); + put(Boolean.FALSE, asList(1, 3)); + }}; + + Map> partition = wrap(list).partition(t -> (t & 1) == 0, Collectors.toList()); + Assert.assertEquals(map, partition); + partition = wrap(list).partition(t -> (t & 1) == 0); + Assert.assertEquals(map, partition); + } + + @Test + public void testForEachIdx() { + final List elements = new ArrayList<>(); + final 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() { + final List elements = new ArrayList<>(); + final 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() { + final List elements = new ArrayList<>(); + wrap(1, 2, 3).forEachOrdered(elements::add); + Assert.assertEquals(asList(1, 2, 3), elements); + } + + @Test + public void testForEach() { + final List elements = new ArrayList<>(); + wrap(1, 2, 3).forEach(elements::add); + Assert.assertEquals(asList(1, 2, 3), elements); + } + + @Test + public void testMapToInt() { + final int[] array = wrap(1, 2, 3).mapToInt(Integer::intValue).toArray(); + Assert.assertArrayEquals(new int[] {1, 2, 3}, array); + } + + @Test + public void testMapToLong() { + final long[] array = wrap(1L, 2L, 3L).mapToLong(Long::intValue).toArray(); + Assert.assertArrayEquals(new long[] {1L, 2L, 3L}, array); + } + + @Test + public void testMapToDouble() { + final 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() { + final int[] array = wrap(1, 2, 3).flatMapToInt(IntStream::of).toArray(); + Assert.assertArrayEquals(new int[] {1, 2, 3}, array); + } + + @Test + public void testFlatMapToLong() { + final long[] array = wrap(1L, 2L, 3L).flatMapToLong(LongStream::of).toArray(); + Assert.assertArrayEquals(new long[] {1L, 2L, 3L}, array); + } + + @Test + public void testFlatMapToDouble() { + final 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() { + final List list = wrap(3, 1, 2).sorted().toList(); + Assert.assertEquals(asList(1, 2, 3), list); + } + + @Test + public void testPeek() { + final List elements = new ArrayList<>(); + wrap(1, 2, 3).peek(elements::add).exec(); + Assert.assertEquals(asList(1, 2, 3), elements); + } + + @Test + public void testPeekIdx() { + final List elements = new ArrayList<>(); + final 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); + + final Set elements2 = new HashSet<>(); + final 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() { + final List list = wrap(1, 2, 3).limit(2L).toList(); + Assert.assertEquals(asList(1, 2), list); + } + + @Test + public void testSkip() { + final 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() { + final Iterator iter1 = Stream.of(1, 2, 3).iterator(); + final Iterator iter2 = wrap(1, 2, 3).iterator(); + while (iter1.hasNext() && iter2.hasNext()) { + Assert.assertEquals(iter1.next(), iter2.next()); + } + } + + @Test + public void testSpliterator() { + final Spliterator iter1 = Stream.of(1, 2, 3).spliterator(); + final 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() { + final AtomicBoolean atomicBoolean = new AtomicBoolean(false); + wrap(Stream.of(1, 2, 3).onClose(() -> atomicBoolean.set(true))).close(); + Assert.assertTrue(atomicBoolean.get()); + } + + @Test + public void testClose() { + final 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() { + final 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() { + final 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() { + final 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() { + final 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() { + final Stream stream = Stream.of(1, 2, 3); + Assert.assertEquals(stream.hashCode(), wrap(stream).hashCode()); + } + + @Test + public void testEquals() { + final Stream stream = Stream.of(1, 2, 3); + Assert.assertEquals(wrap(stream), stream); + } + + @Test + public void testToString() { + final Stream stream = Stream.of(1, 2, 3); + Assert.assertEquals(stream.toString(), wrap(stream).toString()); + } + + @Test + public void testToEntries() { + final 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 unwrap}为{@code null}时抛出 + */ + protected Wrapper(Stream stream) { + super(stream); + } + + @Override + public Wrapper wrap(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 3e38345c1..26fa81ef8 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() { + final Stream stream1 = Stream.of(1, 2); + final 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()).sequential().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 3bbfe072c..7d23e00d1 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 @@ -6,6 +6,7 @@ import org.junit.Test; import java.util.*; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -30,22 +31,39 @@ 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 public void testOf() { - Map map = new HashMap<>(); + final 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<>(); + final 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); + final 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 @@ -55,7 +73,7 @@ public class EntryStreamTest { @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))) + final 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); @@ -63,7 +81,7 @@ public class EntryStreamTest { @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))) + final 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); @@ -71,7 +89,7 @@ public class EntryStreamTest { @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))) + final 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); @@ -79,7 +97,7 @@ public class EntryStreamTest { @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))) + final 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); @@ -87,7 +105,7 @@ public class EntryStreamTest { @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))) + final 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); @@ -95,7 +113,7 @@ public class EntryStreamTest { @Test public void testPeekKey() { - List keys = new ArrayList<>(); + final 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(); @@ -104,7 +122,7 @@ public class EntryStreamTest { @Test public void testPeekValue() { - List values = new ArrayList<>(); + final 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(); @@ -120,12 +138,66 @@ public class EntryStreamTest { .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() { + final Map map1 = new HashMap(){{ + put(1, 1); + put(2, 2); + }}; + final 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() { + final Map map1 = new HashMap(){{ + put(1, 1); + put(2, 2); + }}; + final 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 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))) + final 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( @@ -136,7 +208,7 @@ public class EntryStreamTest { @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))) + final 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( @@ -147,7 +219,7 @@ public class EntryStreamTest { @Test public void testToValueStream() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put(1, 1); map.put(2, 2); map.put(3, 3); @@ -158,7 +230,7 @@ public class EntryStreamTest { @Test public void testToKeyStream() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put(1, 1); map.put(2, 2); map.put(3, 3); @@ -169,7 +241,7 @@ public class EntryStreamTest { @Test public void testCollectKey() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put(1, 1); map.put(2, 2); map.put(3, 3); @@ -179,7 +251,7 @@ public class EntryStreamTest { @Test public void testCollectValue() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put(1, 1); map.put(2, 2); map.put(3, 3); @@ -189,7 +261,7 @@ public class EntryStreamTest { @Test public void testMapKeys() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put(1, 1); map.put(2, 2); map.put(3, 3); @@ -204,7 +276,7 @@ public class EntryStreamTest { @Test public void testMapValues() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put(1, 1); map.put(2, 2); map.put(3, 3); @@ -219,7 +291,7 @@ public class EntryStreamTest { @Test public void testMap() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put(1, 1); map.put(2, 2); map.put(3, 3); @@ -229,11 +301,17 @@ 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 public void testFlatMap() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put(1, 1); map.put(2, 2); map.put(3, 3); @@ -245,11 +323,11 @@ public class EntryStreamTest { @Test public void testFlatMapValue() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put("class1", 1); map.put("class2", 2); map.put("class3", 3); - List values = EntryStream.of(map) + final List values = EntryStream.of(map) .flatMapKey(k -> Stream.of(k + "'s student1", k + "'s student2")) .map((k, v) -> v + "=" + k) .sorted() @@ -266,11 +344,11 @@ public class EntryStreamTest { @Test public void testInverse() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); - List results = EntryStream.of(map) + final List results = EntryStream.of(map) .inverse() .map((k, v) -> k + "=" + v) .collect(Collectors.toList()); @@ -282,11 +360,11 @@ public class EntryStreamTest { @Test public void testFlatMapKey() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put(1, "class1"); map.put(2, "class2"); map.put(3, "class3"); - List values = EntryStream.of(map) + final List values = EntryStream.of(map) .flatMapValue(v -> Stream.of(v + "'s student1", v + "'s student2")) .map((k, v) -> k + "=" + v) .collect(Collectors.toList()); @@ -302,13 +380,13 @@ public class EntryStreamTest { @Test public void testForEach() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put(1, 1); map.put(2, 2); map.put(3, 3); - List keys = new ArrayList<>(); - List values = new ArrayList<>(); + final List keys = new ArrayList<>(); + final List values = new ArrayList<>(); EntryStream.of(map).forEach((k ,v) -> { keys.add(k); values.add(v); @@ -319,7 +397,7 @@ public class EntryStreamTest { @Test public void testToMap() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put(1, 1); map.put(2, 2); map.put(3, 3); @@ -327,7 +405,7 @@ public class EntryStreamTest { Map result = EntryStream.of(map).toMap(); Assert.assertEquals(map, result); - result = EntryStream.of(map).toMap(LinkedHashMap::new); + result = EntryStream.of(map).toMap((Supplier>)LinkedHashMap::new); Assert.assertEquals(new LinkedHashMap<>(map), result); result = EntryStream.of(map).toMap(LinkedHashMap::new, (t1, t2) -> t1); @@ -336,7 +414,7 @@ public class EntryStreamTest { @Test public void testToTable() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put(1, 1); map.put(2, 2); map.put(3, 3); @@ -360,7 +438,7 @@ public class EntryStreamTest { @Test public void testToTableByKey() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put(1, 1); map.put(2, 2); map.put(3, 3); @@ -384,7 +462,7 @@ public class EntryStreamTest { @Test public void testToTableByValue() { - Map map = new HashMap<>(); + final Map map = new HashMap<>(); map.put(1, 1); map.put(2, 2); map.put(3, 3); @@ -408,19 +486,19 @@ public class EntryStreamTest { @Test public void testGroupByKey() { - Map> map1 = EntryStream.of(Arrays.asList(1, 1, 2, 2), Function.identity(), Function.identity()) + final 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()) + final 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()) + final 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)); @@ -453,26 +531,26 @@ public class EntryStreamTest { @Test public void testNonNull() { - Map map = new HashMap<>(); + final 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 public void testKeyNonNull() { - Map map = new HashMap<>(); + final 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 public void testValueNonNull() { - Map map = new HashMap<>(); + final 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 {