首页 文章

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

提问于
浏览
220

我想知道为什么 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();
    }
}

这是一个Hand的实现,因为你可以在玩卡片游戏时手中拿着牌 .

基本上它包装 List<Card> ,确保最大容量并提供一些其他有用的功能 . 将它直接实现为 List<Card> 会更好 .

现在,为了方便起见,我认为实现 Iterable<Card> 会很好,这样如果你想循环它就可以使用增强的for循环 . (我的 Hand 类也提供了 get(int index) 方法,因此 Iterable<Card> 在我看来是合理的 . )

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<T> 没有提供实现 stream()parallelStream() 的默认方法,我什么都看不到会导致这种不可能或不需要的东西?

我找到的一个相关问题如下:Why does Stream<T> not implement Iterable<T>?
奇怪的是,这表明它在某种程度上是相反的 .

3 回答

  • 268

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

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

    Lambda Libs Spec Experts

    我在Lambda Libs Spec Experts mailing list找到了关于这个问题的讨论:

    根据Iterable/Iterator.stream() Sam Pullara说:

    我正在与Brian合作,看看如何实现限制/子流功能[1],他建议转换为Iterator是正确的方法 . 我曾考虑过这个解决方案,但没有找到任何明显的方法来获取迭代器并将其转换为流 . 事实证明它就在那里,你只需要首先将迭代器转换为分裂器,然后将分裂器转换为流 . 因此,这让我重新审视我们是否应该直接将Iterable / Iterator中的一个挂起或两者都挂掉 . 我的建议是至少在Iterator上使用它,这样你就可以在两个世界之间干净利落地移动它也很容易被发现而不必去做:Streams.stream(Spliterators.spliteratorUnknownSize(iterator,Spliterator.ORDERED))

    然后Brian Goetz responded

    我认为Sam的观点是,有很多库类可以为您提供迭代器,但不要让您必须编写自己的分类器 . 所以你所能做的就是调用stream(spliteratorUnknownSize(iterator)) . Sam建议我们定义Iterator.stream()来为你做这件事 . 我想将stream()和spliterator()方法保留为库编写者/高级用户 .

    And later

    “鉴于编写Spliterator比编写Iterator更容易,我宁愿只编写一个Spliterator而不是Iterator(Iterator就是90s :)”但你错过了这一点 . 有数以万计的课程已经给你一个迭代器 . 他们中的许多人并不是分裂者准备好的 .

    Previous Discussions in Lambda Mailing List

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

    用Brian Goetz的话说Streams from Iterable

    退一步......有很多方法可以创建一个Stream . 有关如何描述元素的信息越多,流库可以为您提供的功能和性能就越多 . 对于大多数信息的顺序,它们是:迭代器迭代器大小Spliterator Spliterator知道它的大小Spliterator知道它的大小,并进一步知道所有子分裂知道它们的大小 . (有些人可能会惊讶地发现,在Q(每个元素的工作)非常重要的情况下,我们甚至可以从一个愚蠢的迭代器中提取并行性 . )如果Iterable有一个stream()方法,它只会用一个Spliterator包装一个Iterator,没有尺寸信息 . 但是,大多数可迭代的东西都有尺寸信息 . 这意味着我们正在为缺乏的流提供服务 . 那不太好 . Stephen在此处概述的接受Iterable而不是Collection的API实践的一个缺点是,您正在通过“小管道”强制执行操作,因此在可能有用时丢弃大小信息 . 如果您所做的一切都是为了它,那就没关系了,但如果你想做更多,如果你能保留你想要的所有信息,它会更好 . Iterable提供的默认值确实很糟糕 - 即使绝大多数Iterables都知道这些信息,它也会丢弃大小 .

    Contradiction?

    虽然看起来讨论是基于专家组对最初基于迭代器的Streams的初始设计所做的更改 .

    即便如此,有趣的是注意到在像Collection这样的接口中,stream方法定义为:

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

    哪一个可能与Iterable接口中使用的代码完全相同 .

    所以,这就是为什么我说这个答案可能并不令人满意,但对于讨论仍然很有意思 .

    Evidence of Refactoring

    继续在邮件列表中进行分析,看起来splitIterator方法最初位于Collection界面中,并且在2013年的某些时候,它们将其移动到Iterable .

    Pull splitIterator up from Collection to Iterable .

    Conclusion/Theories?

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

    如果还有其他原因则不明显 . 别人有其他理论吗?

  • 21

    这不是遗漏; 2013年6月对EG清单进行了详细讨论 .

    对专家组的最终讨论植根于this thread .

    虽然"obvious"(最初是专家组)似乎 stream() 似乎在 Iterable 上有意义,但 Iterable 如此普遍的事实成了一个问题,因为明显的签名:

    Stream<T> stream()
    

    并不总是你想要的 . 例如, Iterable<Integer> 的某些内容宁愿使其stream方法返回 IntStream . 但是将 stream() 方法放在层次结构中会使这个问题变得不可能 . 因此,通过提供 spliterator() 方法,我们可以很容易地从 Iterable 创建一个 Stream . Collectionstream() 的实现只是:

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

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

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

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

  • 5

    如果您知道大小,可以使用 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<E> spliterator() .

相关问题