如何将流变成可迭代的?

Bas*_*que 24 java iterable java-stream

Stream继承一个iterator()方法来生成一个Iterator.

但我需要一个Iterable而不是一个Iterator.

例如,给定这个字符串:

String input = "this\n" +
        "that\n" +
        "the_other";
Run Code Online (Sandbox Code Playgroud)

...我需要将字符串的这些部分作为 anIterable传递给特定的库。调用input.lines()产生一个Stream. 所以如果我能把它Stream变成Iterable它的一个元素,我会很高兴的。

Hol*_*ger 28

正如为什么 Stream<T> 不实现 Iterable<T>?,aIterable承担能够提供Iterator不止一次的期望,而 aStream无法实现。因此,虽然您可以为临时使用创建一个Iterableout of a Stream,但您必须小心是否可能存在多次迭代它的尝试。

既然您说“我需要将字符串的这些部分作为 anIterable传递给特定的库”,那么没有通用的解决方案,因为使用 的代码Iterable超出了您的控制范围。

但是,如果您是创建流的人,则可以创建一个有效的Iterable,它会在每次Iterator请求时简单地重复流构造:

Iterable<String> lines = () -> "this\nthat\nthe_other".lines().iterator();
Run Code Online (Sandbox Code Playgroud)

这满足了支持任意次数迭代的期望,同时在仅遍历一次时不会消耗比单个流更多的资源。

for(var s: lines) System.out.println(s);
lines.forEach(System.out::println);
System.out.println(String.join("\n", lines));
Run Code Online (Sandbox Code Playgroud)

  • @KlitosKyriacou 形式的方法引用 `expression::name` 永远不会与 lambda 表达式相同,甚至对于 `System.out::println` 也不一样。但在这里,我们有一个重要的案例 (4认同)
  • 很好的解释。可能值得添加(作为稍微更高级的附录),虽然可以说 `Iterable&lt;String&gt;lines = "this\nthat\nthe_other".lines()::iterator;` 这是罕见的情况之一其中方法引用与 lambda 不同,实际上会产生不良效果。 (3认同)
  • @matt 好吧,从形式上来说,这是不一样的。`expression::name` 的意思是“*计算 `expression` 一次并捕获结果*”,而 `() -&gt; expression.name(…)` 的意思是“*每次计算函数体时计算 `expression`*” 。当“表达式”只是一个局部变量时,差异很小,但即使如此,它也是不同的,即当“stream”为`空`。所以我的说法仍然成立,“表达式::名称”始终与 lambda 表达式不同,问题是,它对我的​​特定用例有多大影响。 (3认同)
  • @matt 没有人说 `stream::iterator` 和 `() -&gt; stream.iterator()` 之间有区别。事实上,我的回答准确地说,没有通用的解决方案可以将现有流转换为允许多次迭代的有效迭代。每次请求迭代器时重复流构造,即这里意味着每次都调用 `lines()`,这就是区别所在。 (2认同)

Bas*_*que 6

tl;博士

只需投射,无需转换。

投射Stream<String>::iteratorIterable<String>.

细节

注意请参阅 Holger 的回答,解释使用流支持的Iterable.

是的,您可以IterableStream.

解决方案很简单,但并不明显。见这个职位莫里斯·纳夫特尔林的lambda常见问题解答

返回 aiteratorBaseStream(的超类Stream)方法的签名Iterator与函数接口的唯一方法匹配Iterable,因此Stream<T>::iterator可以将方法引用用作 的实例Iterable<T>。(这两种方法的名称相同纯属巧合。)

进行输入。

String input = "this\n" +
        "that\n" +
        "the_other";
Stream<String> stream = input.lines() ;
Run Code Online (Sandbox Code Playgroud)

使用方法引用生成一个Iterable<String>.

Iterable< String > iterable = stream::iterator;
Run Code Online (Sandbox Code Playgroud)

测试结果。

for ( String s : iterable ) 
{
    System.out.println( "s = " + s );
}
Run Code Online (Sandbox Code Playgroud)

查看此代码在 IdeOne.com 上实时运行

s = 这个

s = 那

s = the_other

CAVEAT注意流支持的风险IterableHolger在正确答案中进行了解释。

  • 从技术上讲,您不是从“Stream&lt;String&gt;”进行转换,因为“stream::iterator”不是“Stream&lt;String&gt;”类型。 (7认同)
  • @Slaw,问题是,存在强制转换是正确的,但说强制转换是解决方案是不正确的。该解决方案由方法引用和强制转换组成。可以用提供预期目标类型的另一个构造来替换强制转换,例如将方法引用传递给需要可迭代的方法或将其分配给可迭代类型的变量。但该解决方案仍然需要方法引用或 lambda 表达式来进行转换。因此,当仍然存在转换(适配器代码)*和*转换时,说“*只是转换,而不是转换*”是无意义的。 (4认同)
  • 我不确定铸造是正确的术语。您正在通过方法引用“创建”“Iterable”的实例;`(Iterable&lt;String&gt;)` 只是告诉编译器哪个函数接口是目标。 (3认同)
  • Iterable 实际上是一个函数式接口,您正在使用被stream.iterator 覆盖的迭代器方法创建一个Iterable。它实际上是 `(Iterable&lt;String&gt;)()-&gt;stream.iterator()` 或更明确的 `new Iterable&lt;String&gt;(){ public Iterator&lt;String&gt; iterator(){ return stream.iterator(); } }`。因此,您不会将 Stream 强制转换为 Iterable,否则会失败。 (3认同)
  • 不起作用。`(Iterator&lt;String&gt;)"one\ntwo".lines();` 产生。`线程“main”java.lang.ClassCastException 中的异常:类 java.util.stream.ReferencePipeline$Head 无法转换为类 java.util.Iterator`,来自 openjdk 版本“11.0.6”2020-01-14。您的代码可以编译,但会引发异常。我不知道你实际上在运行什么。 (2认同)
  • 您的示例与您粘贴的代码不同!您**仍在使用 ::iterator** 这是一个方法引用。您通过文字转换粘贴的代码已损坏。 (2认同)