如何将Java枚举转换为流(Stream)?

98

我有一个第三方库,它给了我一个 Enumeration<String>。 我想懒惰地将该枚举作为Java 8 Stream进行处理,并调用诸如filtermapflatMap等函数。

是否有现成的库可以实现这个功能?我已经在引用 Guava 和 Apache Commons,如果其中任何一个有解决方案,那就太好了。

另外,将 Enumeration 转换为 Stream 并保留一切的懒惰特性,哪种方法最好/最容易?


1
相关:在Java 8中迭代枚举 - Nathan Hughes
1
链接的问题询问如何将Enumeration(Java 1.0)转换为Iterator(Java 1.2)。我正在询问如何将其转换为Stream(Java 1.8)。虽然似乎链接问题中的最后一个答案回答了这个问题,但该答案对于所提出的问题是错误的。应该在此提供该答案,以便未来的搜索者可以成功找到它。也许@ArneBurmeister想要复制答案到这里,以便直接回答这个问题? - Micah Zoltu
4
因为链接问题的答案没有讨论“懒惰行为”,而且那也不是发布创建Stream的替代方法的合适地方(因为这不是链接问题的范围),所以重新打开此问题。 - Holger
5个回答

186

为什么不使用标准Java:

Collections.list(enumeration).stream()...

然而,正如@MicahZoltu所提到的那样,枚举中项目的数量必须考虑在内,因为Collections.list将首先迭代枚举以将元素复制到ArrayList中。从那里可以使用常规的stream方法。尽管这对于许多集合流操作来说很常见,但如果枚举太大(例如无限),这可能会导致问题,因为必须将枚举转换为列表,然后应改用此处描述的其他方法。


19
这将列举整个“枚举”,使其成为一个列表,然后让您对该列表进行流式访问。如果枚举很小,这可能是一个合理的方法。但是,如果枚举非常大,这可能是一种昂贵且不必要的操作。如果枚举是无限的,这将导致您的应用程序崩溃。 - Micah Zoltu
1
@MicahZoltu 的确。这是需要考虑的一点,我会更新答案。谢谢。 - bric3

62

这个答案已经提供了将 Enumeration 转为 Stream 的解决方案:

 public static <T> Stream<T> enumerationAsStream(Enumeration<T> e) {
     return StreamSupport.stream(
         Spliterators.spliteratorUnknownSize(
             new Iterator<T>() {
                 public T next() {
                     return e.nextElement();
                 }
                 public boolean hasNext() {
                     return e.hasMoreElements();
                 }
             },
             Spliterator.ORDERED), false);
 }
应该强调的是,产生的Stream与其他任何Stream一样惰性,只有在终端操作开始之后才会处理任何项,并且如果终端操作是短路的,则只迭代所需数量的项。
尽管如此,它仍有改进的空间。当有一种简单直接的方法来处理所有元素时,我总是会添加一个forEachRemaining方法。大多数非短路操作都将由Stream实现调用该方法。
public static <T> Stream<T> enumerationAsStream(Enumeration<T> e) {
    return StreamSupport.stream(
        Spliterators.spliteratorUnknownSize(
            new Iterator<T>() {
                public T next() {
                    return e.nextElement();
                }
                public boolean hasNext() {
                    return e.hasMoreElements();
                }
                public void forEachRemaining(Consumer<? super T> action) {
                    while(e.hasMoreElements()) action.accept(e.nextElement());
                }
            },
            Spliterator.ORDERED), false);
}

然而,上述代码属于“使用Iterator因为它非常熟悉”的反模式。创建的Iterator将被包装成新Spliterator接口的实现,并且与直接实现Spliterator相比没有任何优势:
public static <T> Stream<T> enumerationAsStream(Enumeration<T> e) {
    return StreamSupport.stream(
        new Spliterators.AbstractSpliterator<T>(Long.MAX_VALUE, Spliterator.ORDERED) {
            public boolean tryAdvance(Consumer<? super T> action) {
                if(e.hasMoreElements()) {
                    action.accept(e.nextElement());
                    return true;
                }
                return false;
            }
            public void forEachRemaining(Consumer<? super T> action) {
                while(e.hasMoreElements()) action.accept(e.nextElement());
            }
    }, false);
}

在源代码层面上,这种实现方式与基于 Iterator 的实现一样简单,但是省略了从 Spliterator 委托到 Iterator 的过程。它只需要读者了解新的 API 即可。

很好的东西,Holger。在这里使用Spliterator.ORDERED相比其他值有什么优势? - IcedDante
9
“ORDERED”表示有定义的遍历顺序,因此它意味着流实现不允许基于假设数据无序进行优化。由于对于未知的“Enumeration”,我们不知道顺序是否有意义,因此必须假定它可能具有意义并指定此特征。当调用者知道顺序对于特定数据是无关紧要的时候,仍然可以在流上调用“unordered()”以启用优化。但我们的初始前提必须是顺序可能很重要。 - Holger
4
请注意,在Java 9中有Enumeration#asIterator()方法。 - dan1st

54

在Java 9中,可以使用一行代码将Enumeration转换为Stream

Enumeration<String> en = ... ;
Stream<String> str = StreamSupport.stream(
    Spliterators.spliteratorUnknownSize(en.asIterator(), Spliterator.ORDERED),
    false
);

(嗯,这是一行相当长的代码。)

如果您不使用Java 9,则可以使用Holger的答案中提供的技术手动将枚举转换为迭代器


34
如果我们把一行变得足够长,几乎可以把所有东西都写成一行。 ;^) - Holger

13

根据Guava文档,您可以使用Iterators.forEnumeration()方法:

Enumeration<Something> enumeration = ...;

Iterator<SomeThing> iterator = Iterators.forEnumeration(enumeration);

而且在这个问题中,解释了如何从迭代器获取流:

Stream<Something> stream = StreamSupport.stream(
    Spliterators.spliteratorUnknownSize(
        iterator, Spliterator.ORDERED),
    false);

虽然在这种情况下它可能有效,但那个可迭代对象并不是真正的可迭代对象:你只能迭代一次! - dfogni
@dfogni 流也是一样的。他们说用完就丢弃 :) - fps
在上下文中这样做是完全可以的,但它存在一个潜在的 bug,因为它声明了一个与其接口所说的语义不同的对象。更重要的是,在我看来,这是对 Iterable 接口的滥用,它碰巧可以从 lambda 赋值,但并不应该是一个 @FunctionalInterface。 - dfogni

7
在我的StreamEx库中,有一个简单的方法StreamEx.of(Enumeration)可以完成该任务:
Stream<String> stream = StreamEx.of(enumeration);

请注意,这不仅是对 @Holger 解决方案的简单快捷方式,而是以不同的方式实现。特别地,与涉及 Spliterators.spliteratorUnknownSize() 的解决方案相比,它具有明显更好的并行执行特性。

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