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

ski*_*iwi 234 java java-8

我想知道为什么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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

基本上它包装a 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你可以获得一个流:

Stream<Hand> stream = StreamSupport.stream(hand.spliterator(), false);
Run Code Online (Sandbox Code Playgroud)

所以关于真正的问题:

  • 为什么Iterable<T>不提供实现一个默认的方法stream()parallelStream(),我什么也看不到,这将使这个不可能的或不想要的?

我发现的一个相关问题如下:为什么Stream <T>没有实现Iterable <T>?
奇怪的是,这表明它在某种程度上是相反的.

Bri*_*etz 288

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

对专家组的最终讨论植根于这一主题.

虽然看似"显而易见"(即使对最初的专家组而言)stream()似乎有意义Iterable,但是Iterable如此普遍的事实成为一个问题,因为明显的签名:

Stream<T> stream()
Run Code Online (Sandbox Code Playgroud)

并不总是你想要的.例如,有些东西Iterable<Integer>宁愿让他们的流方法返回IntStream.但是将这种stream()方法放在层次结构中会使这种情况变得不可能.因此,相反,我们把它很容易使一个StreamIterable,通过提供一个spliterator()方法.stream()in 的实现Collection只是:

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}
Run Code Online (Sandbox Code Playgroud)

任何客户端都可以通过以下方式获取他们想要的流Iterable:

Stream s = StreamSupport.stream(iter.spliterator(), false);
Run Code Online (Sandbox Code Playgroud)

最后,我们的结论是,加入stream()Iterable将是一个错误.

  • 我觉得奇怪的是上面的逻辑应该被应用于Iterable(我不能有stream()因为有人可能希望它返回IntStream)而没有给予相同数量的思想来向Collection添加完全相同的方法(我可能希望我的Collection <Integer>的stream()也返回IntStream.)无论是两者都存在还是两者都没有,人们可能刚刚接受了他们的生活,但是因为它存在于一个并且没有另一方面,它变得非常明显...... (129认同)
  • 虽然这是有道理的,是有一个原因没有替代静态`Stream.of(可迭代)`,这将至少使该方法合理地发现通过阅读API文档 - 正如有人谁从未真正合作过流的内部我从来没有**看在`StreamSupport`,这是在文档中为提供"低级操作"是"多为图书馆的作家"描述. (40认同)
  • 我完全赞同朱尔斯.应该添加静态方法Stream.of(Iteratable iter)或Stream.of(Iterator iter),而不是StreamSupport.stream(iter.spliterator(),false); (9认同)
  • 我首先要感谢回答这个问题.我仍然很好奇为什么一个`Iterable <Integer>`(我想你正在谈论?)想要返回一个`IntStream`.那么iterable会不会是一个'PrimitiveIterator.OfInt`吗?或者你可能意味着另一个用例? (8认同)
  • Brian McCutchon:这对我来说更有意义.听起来人们只是厌倦了争论并决定安全地玩. (6认同)
  • 也许,但你也不知道.Iterable太笼统了; 我们添加到Iterable的任何默认方法都隐式地约束了Iterable的契约(更糟糕的是,事后).我们通过暴露spliterator()(它本身就是迭代器的概括,因此这并不是一个延伸,因此很容易从Iterable获得一个流.)但是谨慎决定我们就此止步. (4认同)
  • 只是注意到最后一个代码示例没有编译AFAICT.供应商差异有三个参数,而不是一个. (3认同)
  • 为什么`Iterable`不能有'stream()`,那么实现者可以添加`intStream()`如果适用的话?另外,我怀疑你描述的问题会发生的情况很少见. (2认同)

Edw*_*rzo 23

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

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

Lambda Libs Spec专家

我在Lambda Libs Spec Experts邮件列表中找到了关于此的讨论:

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

我正在与Brian一起研究如何实现限制/子流功能[1],他建议转换为Iterator是正确的方法.我曾考虑过这个解决方案,但没有找到任何明显的方法来获取迭代器并将其转换为流.事实证明它就在那里,你只需要先将迭代器转换为分裂器,然后将分裂器转换为流.所以这让我重新审视一下我们是否应该直接将Iterable/Iterator中的一个挂起或两者都挂掉.

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

Streams.stream(Spliterators.spliteratorUnknownSize(iterator,Spliterator.ORDERED))

然后Brian Goetz做出回应:

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

我想将stream()和spliterator()方法保留为库编写者/高级用户.

然后

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

但是你错过了这一点.有数以万计的课程已经给你一个迭代器.他们中的许多人都没有准备好分裂者.

以前在Lambda邮件列表中的讨论

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

来自Iterable的Streams下的Brian Goetz的话来说:

退后一步......

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

迭代器

迭代器+大小

Spliterator

Spliterator知道它的大小

Spliterator知道它的大小,并且进一步知道所有子分裂都知道它们的大小.

(有些人可能会惊讶地发现,在Q(每个元素的工作)非常重要的情况下,我们甚至可以从一个愚蠢的迭代器中提取并行性.)

如果Iterable有一个stream()方法,它只会用一个没有大小信息的Spliterator包装一个Iterator.但是,大部分的东西都是可迭代有尺寸信息.这意味着我们正在提供不足的流.那不太好.

Stephen在此处概述的接受Iterable而不是Collection的API实践的一个缺点是,您正在通过"小管道"强制执行操作,因此在可能有用时丢弃大小信息.如果您要做的就是为了它,那就没关系了,但如果你想做更多,如果你能保留你想要的所有信息,它会更好.

Iterable提供的默认值确实很糟糕 - 即使绝大多数Iterables都知道这些信息,它也会丢弃大小.

矛盾?

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

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

default Stream<E> stream() {
   return StreamSupport.stream(spliterator(), false);
}
Run Code Online (Sandbox Code Playgroud)

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

所以,这就是为什么我说这个答案可能不太令人满意,但仍然有趣的讨论.

重构的证据

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

将splitIterator从Collection转为Iterable.

结论/理论?

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

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


Udo*_*Udo 6

如果您知道可以使用的大小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();
   }
}
Run Code Online (Sandbox Code Playgroud)

然后:

new Hand().stream().map(...)
Run Code Online (Sandbox Code Playgroud)

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

你还应该考虑覆盖Spliterator<E> spliterator().