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


241

我想知道为什么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<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(),我什么也看不到,因此不可能或不需要?

我发现的一个相关问题如下:为什么Stream <T>不实现Iterable <T>?
奇怪的是,这暗示它可以反过来做。


1
我想这对于Lambda邮件列表是个好问题。
Edwin Dalorzo 2014年

为什么要在流中进行迭代为什么很奇怪?您还可能如何break;进行迭代?(好的,Stream.findFirst()可能是一个解决方案,但可能无法满足所有需求...)
glglgl 2014年

另请参阅使用Java 8 JDK将Iterable转换为Stream以获得实际解决方法。
Vadzim

Answers:


297

这不是遗漏;2013年6月,对EG列表进行了详细讨论。

专家组的权威性讨论源于此主题

尽管似乎“显而易见”(即使是对专家组stream()来说也是如此)似乎很有意义Iterable,但Iterable如此普遍的事实却成为一个问题,因为显而易见的签名是:

Stream<T> stream()

并不总是您想要的。例如,有些Iterable<Integer>本来希望其流方法返回的东西IntStream。但是,将这种stream()方法放在层次结构中的较高位置将使这成为不可能。因此,相反,我们把它很容易使一个StreamIterable,通过提供一个spliterator()方法。stream()in 的实现Collection只是:

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

任何客户都可以通过以下方式从中获得所需的流Iterable

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

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


8
我首先看到非常感谢您回答这个问题。我仍然很好奇,尽管为什么Iterable<Integer>(我想您在说什么?)为什么要返回IntStream。那可迭代的话就不是PrimitiveIterator.OfInt吗?还是您可能意味着另一个用例?
skiwi 2014年

139
我觉得奇怪的是,上述逻辑被认为适用于Iterable(我不能拥有stream(),因为有人可能希望它返回IntStream),而对于将完全相同的方法添加到Collection中并没有给予同等的考虑(我可能希望我的Collection <Integer>的stream()也返回IntStream。)无论是同时存在还是不存在,人们都可能会继续生活,但是因为它同时存在而又不在另一方面,它变得非常明显...
Trejkaz 2015年

6
Brian McCutchon:对我来说更有意义。听起来人们只是厌倦了争吵并决定安全播放。
乔纳森·洛克

44
虽然这是有道理的,但有没有理由为什么没有替代的static Stream.of(Iterable),这至少可以通过阅读API文档使该方法合理地被发现-因为从未真正使用过Streams内部的人从来没有甚至看过StreamSupport,在文档中将其描述为提供“主要用于库作者”的“低级操作”。
Jules

9
我完全同意朱尔斯。应该添加静态方法Stream.of(Iteratable iter)或Stream.of(Iterator iter),而不是StreamSupport.stream(iter.spliterator(),false);
user_3380739 '16

23

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

到目前为止,我还没有找到令人满意的解释。阅读所有这些内容后,我得出结论,这只是一个遗漏。但是您可以在这里看到,在API的设计过程中,多年来对此进行了多次讨论。

Lambda Libs Spec专家

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

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

我正在与Brian一起研究如何实现限制/子流功能[1],他建议转换为Iterator是正确的方法。我曾考虑过该解决方案,但没有找到任何明显的方法来采用迭代器并将其转换为流。事实证明它就在其中,您只需要先将迭代器转换为拆分器,然后将拆分器转换为流即可。因此,这使我重新审视了是否应该直接将这些挂起于Iterable / Iterator之一或同时挂起。

我的建议是至少在Iterator上安装它,这样您就可以在两个世界之间轻松地移动,而且也很容易发现它,而不必这样做:

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

然后Brian Goetz回答

我认为Sam的观点是,有很多库类可以为您提供Iterator,但不必让您一定要编写自己的splitter。因此,您所能做的就是调用stream(spliteratorUnknownSize(iterator))。Sam建议我们定义Iterator.stream()为您做到这一点。

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

然后

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

但是,您错过了重点。那里有成千上万的类已经为您提供了迭代器。而且其中许多还不支持分割器。

Lambda邮件列表中的先前讨论

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

在布赖恩戈茨下的话,从可迭代流

向后退...

创建流的方法有很多。您拥有有关如何描述元素的更多信息,流库可以为您提供更多的功能和性能。至少从大多数信息的角度来看,它们是:

迭代器

迭代器+大小

分流器

知道其大小的分离器

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

(在Q(每个元素的工作量)不平凡的情况下,甚至可以从哑迭代器中提取并行性,这可能使某些人感到惊讶。)

如果Iterable具有stream()方法,则它将只包装一个带有Spliterator的Iterator,而没有大小信息。但是,大多数可迭代的事物确实具有大小信息。这意味着我们正在提供不足的流。那不是很好

Stephen在此处概述的API做法的一个缺点是,它接受Iterable而不是Collection,这是迫使您通过“小管道”强制执行操作,因此在可能有用时会丢弃大小信息。如果您要做的只是每个工作就可以了,但是如果您想做更多的事情,那么可以保留所有想要的信息就更好了。

Iterable提供的默认值确实是糟糕透顶的-即使绝大多数Iterables都知道该信息,它也会丢弃大小。

矛盾?

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

即便如此,有趣的是,在诸如Collection之类的接口中,stream方法定义为:

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

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

因此,这就是为什么我说这个答案可能不令人满意,但对于讨论仍然很有趣。

重构的证据

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

将splitIterator从Collection向上拉到Iterable

结论/理论?

然后可能是Iterable中缺少该方法只是一个遗漏,因为当他们将splitIterator从Collection移到Iterable时,他们似乎也应该移动stream方法。

如果还有其他原因,这些都不明显。别人还有其他理论吗?


感谢您的答复,但我不同意其中的理由。在你重写的时刻spliterator()Iterable,那么所有的问题有固定的,你可以平凡实现stream()parallelStream()..
skiwi

@skiwi这就是为什么我说这可能不是答案。我只是想增加讨论的范围,因为很难知道专家组为何做出他们的决定。我想我们所能做的就是尝试在邮件列表中进行一些取证,看看是否可以提出任何原因。
Edwin Dalorzo 2014年

1
@skiwi我查看了其他邮件列表,发现了更多的讨论证据,以及一些有助于理论化诊断的想法。
Edwin Dalorzo 2014年

感谢您的努力,我真的应该学习如何有效地拆分这些邮件列表。如果可以以某种现代方式将其可视化,例如论坛之类的东西,这将有所帮助,因为阅读带有引号的纯文本电子邮件并非完全有效。
skiwi 2014年

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();
   }
}

然后:

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

我面临着同样的问题而感到惊讶,我的Iterable执行可以很容易地扩展到AbstractCollection通过简单地将实施size()方法而(幸运的是,我有一个集合的大小:-)

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.