如何从迭代器创建一个Java 8流?

34

是否可以从迭代器中创建一个流,使得对象序列与重复调用迭代器的next()方法生成的对象序列相同?我考虑的是由TreeSet.descendingIterator()返回的迭代器的使用情况,但我可以想象其他情况下只有迭代器而没有它引用的集合。

例如,对于TreeSet<T> tset,我们可以编写tset.stream()...并获得该集合中对象的流,按照集合的排序顺序排列,但如果我们想要它们以不同的顺序排列,例如使用descendingIterator()可用的顺序怎么办?我想象中应该类似tset.descendingIterator().stream()...stream( tset.descendingIterator() )...这样的形式,但这些形式都是无效的。


我对Java 8不是很熟悉,所以我在评论而不是回答,但你是否正在寻找Java的Stream接口?听起来它可能符合您的需求(至少对于某些操作...如果您需要它以更迭代器样式的方式运行,似乎不会起作用) - awksp
@user3580294,术语“stream”不幸地被重载了,但我指的是java.util.stream.Stream<T>接口。我会添加一个例子。 - sdenham
你想从 java.util.Iterator<T> 创建一个 java.util.stream.Stream<T> 对象吗? - awksp
4个回答

53
static <T> Stream<T> iteratorToFiniteStream(final Iterator<T> iterator) {
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
}

static <T> Stream<T> iteratorToInfiniteStream(final Iterator<T> iterator) {
    return Stream.generate(iterator::next);
}

我同意,我知道它可以工作,但我不明白为什么lambda可以分配给Iterable - Brad
第二种解决方案在许多(大多数?)情况下会导致异常。例如:Iterator<Integer> iterator = Arrays.asList(0, 1, 2, 3).iterator(); Stream.generate(iterator::next).forEach(e -> System.out.println(e)); 打印出 0、1、2、3,然后抛出 NoSuchElementException 异常。问题在于从未调用 iterator.hasNext() - jcsahnwaldt Reinstate Monica
1
@JonaChristopherSahnwaldt 因为你的四元素流远离“无限” ;) 你不觉得在第二种方法中不需要检查迭代器“hasNext”吗? - Karol Król
@KarolKról 不,我不这么认为。在许多情况下,第二种方法会导致异常。第一种方法更好,而且它也适用于“无限”迭代器(根据我的经验,这种情况非常少见)。 - jcsahnwaldt Reinstate Monica

36

针对 NavigableSet.descendingIterator() 这个具体示例,我认为最简单的方法是使用 NavigableSet.descendingSet()

但如果你对于更一般化的情况感兴趣,下面的方法似乎可以解决:

import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.TreeSet;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public class Streams {
    public static void main(String... args) {
        TreeSet<String> set = new TreeSet<>();
        set.add("C");
        set.add("A");
        set.add("B");

        Iterator<String> iterator = set.descendingIterator();

        int characteristics = Spliterator.DISTINCT | Spliterator.SORTED | Spliterator.ORDERED;
        Spliterator<String> spliterator = Spliterators.spliteratorUnknownSize(iterator, characteristics);

        boolean parallel = false;
        Stream<String> stream = StreamSupport.stream(spliterator, parallel);

        stream.forEach(System.out::println); // prints C, then B, then A
    }
}
简而言之,你需要先使用Spliterators中的一个静态方法从Iterator创建一个Spliterator。然后,你可以使用StreamSupport中的静态方法创建一个Stream

我对手动创建Spliterators和Streams没有太多的经验,所以我无法真正评论应该是什么特性或者对性能产生什么影响。在这个简单的例子中,似乎设置特性为上述内容或者将其设置为0(即没有特性)并没有什么区别。 Spliterators中还有一种使用初始大小估计创建Spliterator的方法 - 我想在这个简单的例子中,你可以使用set.size(),但如果你想处理任意的Iterators,我猜这不会是情况。同样,我不太确定它对性能产生了什么影响。


1
感谢您提供通用解决方案,并让我注意到NavigableSet。从StreamSupport文档中,我看到java.util.function.Supplier<T>提供了一个接口来将数据输入流中。 - sdenham
请查看Karol的答案,这应该是被接受的答案。 - Jochen

3

这不会创建一个流,但是Iterator也有一个名为forEachRemaining的方法:

someIterator.forEachRemaining(System.out::println);
someIterator.forEachRemaining(s -> s.doSomething());
//etc.

您传递的参数是一个 Consumer,这与您传递给 Stream::forEach 的相同。 这里是该方法的文档。请注意,您无法像使用流一样继续“链接”操作。 但是,在我想将 Iterator 转换为 Stream 的少数几次尝试中,我仍然发现这很有帮助。

1

正如Karol Król所写,对于无限流,您可以使用以下代码:

Stream.generate(iterator::next)

但是你也可以在Java 9之后使用它来处理有限流,例如takeWhile

Stream.generate(iterator::next).takeWhile((v) -> iterator.hasNext())

1
这将跳过迭代器返回的最后一个元素。 - Michael B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接