为啥 Iterable<T> 不提供 stream() 和 parallelStream() 方法?

Posted

技术标签:

【中文标题】为啥 Iterable<T> 不提供 stream() 和 parallelStream() 方法?【英文标题】:Why does Iterable<T> not provide stream() and parallelStream() methods?为什么 Iterable<T> 不提供 stream() 和 parallelStream() 方法? 【发布时间】:2014-05-31 14:18:04 【问题描述】:

我想知道为什么Iterable 接口不提供stream()parallelStream() 方法。考虑以下类:

public class Hand implements Iterable<Card> 
    private final List<Card> list = new ArrayList<>();
    private final int capacity;

    //...

    @Override
    public Iterator<Card> iterator() 
        return list.iterator();
    

这是一种手牌的实现,因为您可以在玩集换式卡牌游戏时手中拿着牌。

基本上它包装了List&lt;Card&gt;,确保了最大容量并提供了一些其他有用的功能。最好直接将其实现为List&lt;Card&gt;

现在,为了方便起见,我认为实现 Iterable&lt;Card&gt; 会很好,这样如果你想循环它,你可以使用增强的 for 循环。 (我的Hand 类也提供了一个get(int index) 方法,因此我认为Iterable&lt;Card&gt; 是合理的。)

Iterable 接口提供以下内容(省略 javadoc):

public interface Iterable<T> 
    Iterator<T> iterator();

    default void forEach(Consumer<? super T> action) 
        Objects.requireNonNull(action);
        for (T t : this) 
            action.accept(t);
        
    

    default Spliterator<T> spliterator() 
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    

现在你可以获得一个流:

Stream<Hand> stream = StreamSupport.stream(hand.spliterator(), false);

所以到真正的问题:

为什么Iterable&lt;T&gt; 不提供实现stream()parallelStream() 的默认方法,我看不出这不可能或不需要?

我发现的一个相关问题如下:Why does Stream<T> not implement Iterable<T>? 奇怪的是,它暗示它以相反的方式去做。

【问题讨论】:

我想这对Lambda Mailing List来说是个好问题。 为什么要遍历流很奇怪?你怎么可能break; 一个迭代? (好吧,Stream.findFirst() 可能是一个解决方案,但可能无法满足所有需求......) 另请参阅Convert Iterable to Stream using Java 8 JDK 了解实际解决方法。 【参考方案1】:

如果您知道可以使用java.util.Collection 的大小,它提供了stream() 方法:

public class Hand extends AbstractCollection<Card> 
   private final List<Card> list = new ArrayList<>();
   private final int capacity;

   //...

   @Override
   public Iterator<Card> iterator() 
       return list.iterator();
   

   @Override
   public int size() 
      return list.size();
   

然后:

new Hand().stream().map(...)

我遇到了同样的问题,我很惊讶我的 Iterable 实现可以通过简单地添加 size() 方法很容易地扩展到 AbstractCollection 实现(幸运的是我有集合的大小:-)

您还应该考虑覆盖Spliterator&lt;E&gt; spliterator()

【讨论】:

【参考方案2】:

这不是遗漏; 2013年6月EG名单有详细讨论。

专家组最终讨论植根于this thread。

虽然stream()Iterable 上似乎“很明显”(甚至对专家组来说也是如此),但Iterable 如此笼统的事实成为一个问题,因为明显的签名:

Stream<T> stream()

并不总是你想要的。例如,有些东西是Iterable&lt;Integer&gt;,宁愿让他们的流方法返回一个IntStream。但是将stream() 方法放在层次结构中的这么高的位置会使这成为不可能。因此,相反,我们通过提供spliterator() 方法使从Iterable 生成Stream 变得非常容易。 stream()Collection中的实现就是:

default Stream<E> stream() 
    return StreamSupport.stream(spliterator(), false);

任何客户端都可以从Iterable 获取他们想要的流:

Stream s = StreamSupport.stream(iter.spliterator(), false);

最后我们得出结论,将stream() 添加到Iterable 是一个错误。

【讨论】:

我明白了,首先,非常感谢您回答这个问题。我仍然很好奇为什么Iterable&lt;Integer&gt;(我想你在谈论?)想要返回IntStream。那么可迭代的不是PrimitiveIterator.OfInt吗?或者您可能是指另一个用例? 我觉得奇怪的是,上面的逻辑应该应用于 Iterable(我不能有 stream(),因为有人可能希望它返回 IntStream),而没有给予同等数量的思考向 Collection 添加完全相同的方法(我可能希望我的 Collection 的 stream() 也返回 IntStream。)无论两者都存在还是不存在,人们可能会继续他们的生活,但是因为一个存在而另一个不存在,所以它变得非常明显的遗漏...... Brian McCutchon:这对我来说更有意义。听起来人们只是厌倦了争论并决定谨慎行事。 虽然这是有道理的,但是否有理由没有替代静态 Stream.of(Iterable),这至少可以通过阅读 API 文档来合理地发现该方法——作为一个从来没有在 StreamSupport 上我什至从未看过 Streams 的内部结构,文档中将其描述为提供“主要用于库编写者”的“低级操作”。跨度> 我完全同意朱尔斯的观点。应添加静态方法 Stream.of(Iteratable iter) 或 Stream.of(Iterator iter),而不是 StreamSupport.stream(iter.spliterator(), false);【参考方案3】:

我对几个项目 lambda 邮件列表进行了调查,我想我发现了一些有趣的讨论。

到目前为止,我还没有找到令人满意的解释。看完这一切后,我得出结论,这只是一个遗漏。但是您可以在这里看到,多年来在 API 的设计过程中多次讨论过它。

Lambda Libs 规范专家

我在Lambda Libs Spec Experts mailing list发现了一个关于这个的讨论:

在Iterable/Iterator.stream()Sam Pullara 下说:

我正在与 Brian 一起研究如何限制/子流 功能[1] 可能会实现,他建议转换为 迭代器是正确的方法。我曾想过 解决方案,但没有找到任何明显的方法来获取迭代器并转向 它变成一个流。原来它在那里,你只需要先 将迭代器转换为拆分器,然后将拆分器转换 到一条溪流。所以这让我重新审视我们是否应该拥有 这些直接或两者都挂在 Iterable/Iterator 之一上。

我的建议是至少将它放在 Iterator 上,这样你就可以移动 在两个世界之间干净利落,也很容易 可发现而不是必须做:

Streams.stream(Spliterators.spliteratorUnknownSize(迭代器, Spliterator.ORDERED))

然后Brian Goetz responded:

我认为 Sam 的意思是有很多库类 给你一个迭代器,但不要让你一定要自己写 分裂器。所以你所能做的就是打电话 流(spliteratorUnknownSize(迭代器))。山姆建议我们 定义 Iterator.stream() 来为你做这件事。

我想保持 stream() 和 spliterator() 方法不变 供图书馆作者/高级用户使用。

And later

“鉴于编写 Spliterator 比编写 Iterator 更容易, 我宁愿只写一个 Spliterator 而不是 Iterator (Iterator 是 90 年代 :)"

不过,你没有抓住重点。有数以万计的课程 已经给你一个迭代器。他们中的许多人不是 拆分器就绪。

Lambda 邮件列表中的先前讨论

这可能不是您正在寻找的答案,但在Project Lambda mailing list 中对此进行了简要讨论。也许这有助于促进对该主题进行更广泛的讨论。

用 Streams from Iterable 下的 Brian Goetz 的话来说:

退一步……

有很多方法可以创建 Stream。您提供的信息越多 有关于如何描述元素,更多的功能和 流库可以为您提供的性能。以最少的顺序 大多数信息,它们是:

迭代器

迭代器 + 大小

分离器

知道其大小的分离器

知道其大小的拆分器,并进一步知道所有子拆分 知道它们的大小。

(有些人可能会惊讶地发现我们甚至可以提取并行性 在 Q(每个元素的工作量)为 不平凡。)

如果 Iterable 有一个 stream() 方法,它只会包装一个 Iterator 一个 Spliterator,没有大小信息。但是,大多数事情是 可迭代 do 有大小信息。这意味着我们正在服务 流不足。这不太好。

Stephen 在这里概述的 API 实践的一个缺点是 接受 Iterable 而不是 Collection,是你在强迫 通过“小管道”的东西,因此丢弃大小 可能有用的信息。没关系,如果你正在做的一切 to do is forEach it,但如果你想做更多,最好 可以保留您想要的所有信息。

Iterable 提供的默认设置确实很糟糕——它 即使绝大多数 Iterables 确实知道,也会丢弃大小 这些信息。

矛盾?

不过,看起来讨论是基于专家组对最初基于迭代器的 Streams 的初始设计所做的更改。

即便如此,有趣的是,在 Collection 这样的接口中,流方法定义为:

default Stream<E> stream() 
   return StreamSupport.stream(spliterator(), false);

这可能与 Iterable 接口中使用的代码完全相同。

所以,这就是为什么我说这个答案可能并不令人满意,但仍然值得讨论。

重构的证据

继续邮件列表中的分析,splitIterator 方法似乎最初是在 Collection 接口中,并在 2013 年的某个时候将其移至 Iterable。

Pull splitIterator up from Collection to Iterable.

结论/理论?

那么很有可能 Iterable 中缺少该方法只是一个遗漏,因为看起来他们应该在将 splitIterator 从 Collection 向上移动到 Iterable 时也移动了流方法。

如果还有其他不明显的原因。其他人有其他理论吗?

【讨论】:

感谢您的回复,但我不同意那里的推理。在您覆盖Iterablespliterator() 的那一刻,所有问题都已修复,您可以轻松实现stream()parallelStream().. @skiwi 这就是为什么我说这可能不是答案。我只是想加入讨论,因为很难知道专家组为什么会做出他们所做的决定。我想我们能做的就是尝试在邮件列表中做一些取证,看看我们是否能找到任何理由。 @skiwi 我查看了其他邮件列表,发现了更多的讨论证据,也许还有一些有助于理论化某些诊断的想法。 感谢您的努力,我真的应该学习如何有效地拆分这些邮件列表。如果可以以某种...现代方式将它们可视化会有所帮助,例如论坛或其他方式,因为阅读带有引号的纯文本电子邮件效率不高。

以上是关于为啥 Iterable<T> 不提供 stream() 和 parallelStream() 方法?的主要内容,如果未能解决你的问题,请参考以下文章

为啥没有 getFirst(iterable) 方法?

为啥没有提供 Stream<E> 的接口?

CrudRepository 中的 delete(Iterable<? extends T> itrbl) 和 JpaRepository 中的 deleteInBatch(Ite​​ra

Kotlin 的 Iterable 和 Sequence 看起来完全一样。为啥需要两种类型?

有没有实现 Iterable 而不实现 Collection 的 Java 标准类?

有没有实现 Iterable 而不实现 Collection 的 Java 标准类?