【问题标题】:How do I turn a Java Enumeration into a Stream?如何将 Java 枚举转换为流?
【发布时间】:2016-01-19 11:05:51
【问题描述】:

我有一个第三方库,它给了我Enumeration<String>。我想以 Java 8 Stream 的形式懒惰地使用该枚举,在其上调用 filtermapflatMap 之类的东西。

是否有一个现有的图书馆包含这个?我已经在参考 Guava 和 Apache Commons,所以如果其中任何一个都有理想的解决方案。

或者,将Enumeration 转换为Stream 的最佳/最简单方法是什么,同时保留一切的惰性?

【问题讨论】:

  • 链接的问题是询问如何将 Enumeration (Java 1.0) 转换为 Iterator (Java 1.2)。我在问如何把它变成Stream(Java 1.8)。虽然链接问题中的最后一个答案似乎确实回答了这个问题,但对于所提出的问题,该答案是错误的。应在此处提供该答案,以便将来的搜索者可以成功找到它。也许@ArneBurmeister 想在这里复制答案,以便直接回答这个问题?
  • 重新打开,因为链接问题的答案不讨论懒惰的行为,它也不适合发布创建Stream 的替代方法(因为这不是链接的问题)。

标签: java java-8 java-stream


【解决方案1】:

为什么不使用原生 Java:

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

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

【讨论】:

  • 这将枚举整个enumeration,使其成为一个列表,然后让您流式访问该列表。如果枚举很小,这可能是一种合理的方法。如果枚举非常大,这可能是一项昂贵且不必要的操作。如果枚举是无限的,这将使您的应用程序崩溃。
  • @MicahZoltu 确实如此。这是要考虑的一点,我会更新答案。谢谢
【解决方案2】:

This answer 已经提供了一个解决方案,可以从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 一样简单,但消除了从SpliteratorIterator 的委托。它只要求读者了解新的 API。

【讨论】:

  • 好东西霍尔格。与其他值相比,在此处使用 Spliterator.ORDERED 有什么优势?
  • @IcedD​​ante ORDERED 表示存在已定义的遇到顺序,因此这意味着 Stream 实现不允许基于假设数据无序进行优化.因为对于一个未知的Enumeration,我们不知道这个订单是否有意义,我们必须假设它可能有一个意义并指定这个特征。当调用者知道顺序与特定数据无关时,调用者仍然可以在流上调用unordered(),以启用优化。但我们最初的前提必须是顺序可能很重要。
  • 请注意,从 java 9 开始就有Enumeration#asIterator()
【解决方案3】:

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

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

(嗯,这是一条相当长的线。)

如果您不在 Java 9 上,您可以使用 Holger's answer 中提供的技术手动将 Enumeration 转换为 Iterator

【讨论】:

  • 如果我们把线做得足够长,我们几乎可以把所有东西都做成一条线;^)
【解决方案4】:

根据Guava docs,你可以使用Iterators.forEnumeration()方法:

Enumeration<Something> enumeration = ...;

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

this question 中,解释了如何从迭代器中获取流:

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

【讨论】:

  • 虽然它可能在这种情况下工作,但 iterable 并不是真正的 Iterable:你可以只迭代一次!
  • @dfogni 流也是如此。使用和丢弃,他们说:)
  • 在上下文中它完全没问题,但这是一个等待发生的错误,因为它声明了一个具有与其接口所说的语义不同的对象。更重要的是,恕我直言,这是对 Iterable 接口的滥用,它碰巧可以从 lambda 分配,但不应该是 @FunctionalInterface
【解决方案5】:

在我的 StreamEx 库中,有一个简单的方法 StreamEx.of(Enumeration) 可以完成这项工作:

Stream<String> stream = StreamEx.of(enumeration);

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-07
    • 2017-10-06
    • 1970-01-01
    • 2016-03-20
    • 2010-09-06
    • 1970-01-01
    相关资源
    最近更新 更多