注意:我不一定在寻找下面描述的具体示例问题的解决方案。我真的很感兴趣为什么在Java 8中不能开箱即用。
Java流是懒惰的。最后他们有一个终端操作 我的解释是这个终端操作将通过流提取所有值。没有任何中间操作可以做到这一点。为什么没有中间操作通过流引入任意数量的元素?像这样:
stream
.mapMultiple(this::consumeMultipleElements) // or groupAndMap or combine or intermediateCollect or reverseFlatMap
.collect(Collectors.toList());
当下游操作尝试将流推进一次时,中间操作可能会尝试多次推进上游(或根本不推进)。
我会看到几个用例:
(这些只是示例。所以你可以看到它确实可以处理这些用例,但它不是“流式传输方式”,而且这些解决方案缺乏Streams所具有的理想的懒惰属性。)
将多个元素组合到一个新元素中,以传递到流的其余部分。 (例如,成对(1,2,3,4,5,6) ➔ ((1,2),(3,4),(5,6))
)
// Something like this,
// without needing to consume the entire stream upfront,
// and also more generic. (The combiner should decide for itself how many elements to consume/combine per resulting element. Maybe the combiner is a Consumer<Iterator<E>> or a Consumer<Supplier<E>>)
public <E, R> Stream<R> combine(Stream<E> stream, BiFunction<E, E, R> combiner) {
List<E> completeList = stream.collect(toList());
return IntStream.range(0, completeList.size() / 2)
.mapToObj(i -> combiner.apply(
completeList.get(2 * i),
completeList.get(2 * i + 1)));
}
确定Stream是否为空(将Stream映射到可选的非空流)
// Something like this, without needing to consume the entire stream
public <E> Optional<Stream<E>> toNonEmptyStream(Stream<E> stream) {
List<E> elements = stream.collect(toList());
return elements.isEmpty()
? Optional.empty()
: Optional.of(elements.stream());
}
拥有一个不会终止流的惰性Iterator
(允许基于更复杂的逻辑跳过元素,然后只是skip(long n)
)。
Iterator<E> iterator = stream.iterator();
// Allow this without throwing a "java.lang.IllegalStateException: stream has already been operated upon or closed"
stream.collect(toList());
当他们设计Streams及其周围的一切时,他们是否忘记了这些用例,还是明确地将其删除了? 我知道在处理并行流时这些可能会产生意想不到的结果,但在我看来,这是一个可以记录的风险。
答案 0 :(得分:5)
您想要的所有操作实际上都可以在Stream API
中实现,但不是开箱即用。
将多个元素组合成元素对 - 您需要自定义Spliterator
。这是Tagir Valeev这样做的。他有一个名为StreamEx
的绝对野兽,可以做很多其他有用的东西,不支持开箱即用。
我不明白你的第二个例子,但我敢打赌它也是可行的。
skip
更复杂的操作是java-9
通过dropWhile
和takeWhile
以Predicate
作为输入。
请注意,如果您说没有任何中间操作可以做到这一点并不准确 - 那么sorted
和distinct
就是这样做的。否则他们无法工作。还有flatMap
这样的行为,但这更像是一个错误。
另一件事是并行流的中间操作没有定义的顺序,因此这样的有状态中间操作将具有并行流的未知条目。另一方面,您总是可以选择滥用以下内容:
List<Something> list = Collections.synchronizedList()
.map(x -> {
list.add(x);
// your mapping
})
如果我是你并且真的认为我可能需要那个,我不会这样做,但为了以防万一......
答案 1 :(得分:3)
并非每个终端操作都会“通过流提取所有值”。终端操作iterator()
和spliterator()
不会立即获取所有值并允许进行延迟处理,包括再次构建新的Stream
。对于后者,强烈建议使用spliterator()
,因为这样可以将更多元信息传递给新流,并且意味着更少的对象包装。
E.g。你的第二个例子可以实现为
public static <T> Stream<T> replaceWhenEmpty(Stream<T> s, Supplier<Stream<T>> fallBack) {
boolean parallel = s.isParallel();
Spliterator<T> sp = s.spliterator();
Stream.Builder<T> firstElement;
if(sp.getExactSizeIfKnown()==0 || !sp.tryAdvance(firstElement=Stream.builder())) {
s.close();
return fallBack.get();
}
return Stream.concat(firstElement.build(), StreamSupport.stream(sp, parallel))
.onClose(s::close);
}
对于您的一般问题,我没有看到这些示例的一般抽象应该是什么样子,除了已经存在的spliterator()
方法。正如the documentation所说的那样
但是,如果提供的流操作不提供所需的功能,则可以使用BaseStream.iterator()和BaseStream.spliterator()操作来执行受控遍历。