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

时间:2014-04-16 15:38:05

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

这是 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 个答案:

答案 0 :(得分:276)

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

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

虽然看起来很明显&#34; (即使是最初的专家组)stream() Iterable似乎有意义,Iterable如此普遍的事实成为一个问题,因为显而易见的签名:

Stream<T> stream()

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

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

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

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

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

答案 1 :(得分:23)

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

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

Lambda Libs Spec Experts

我在Lambda Libs Spec Experts mailing list

中找到了对此的讨论

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

  

我正在和Brian一起看看如何限制/子流   功能[1]可能已实施,他建议转换为   迭代器是正确的方法。我考虑过这个   解决方案,但没有找到任何明显的方法来采取迭代器和转向   它变成了流。原来它在那里,你只需要先   将迭代器转换为spliterator然后转换spliterator   到一个流。所以这让我重新审视我们是否应该拥有   这些可以直接挂掉Iterable / Iterator中的一个或两者。

     

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

     

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

然后Brian Goetz responded

  

我认为Sam的观点是有很多库类   给你一个迭代器,但不要让你自己写   spliterator。所以你能做的就是打电话   流(spliteratorUnknownSize(迭代))。山姆在暗示我们   定义Iterator.stream()为你做这件事。

     

我想将stream()和spliterator()方法保持为   对于图书馆作家/高级用户。

And later

  

&#34;鉴于编写Spliterator比编写Iterator更容易,   我更愿意只写一个Spliterator而不是Iterator(Iterator是90s):&#34;

     但是,你错过了这一点。有数以万计的课程   那里已经递给你一个迭代器。其中很多都不是   spliterator就绪

Lambda邮件列表中的先前讨论

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

Streams from Iterable下的Brian Goetz的话说:

  

退后一步......

     

有很多方法可以创建Stream。你的信息越多   有关如何描述元素,功能和更多   流库可以给你带来的性能。按顺序至少   大多数信息,他们是:

     

迭代

     

Iterator + size

     

Spliterator

     

知道其大小的Spliterator

     

Spliterator知道它的大小,并进一步知道所有的子分裂   知道他们的大小。

     

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

     

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

     

Stephen在这里概述的API实践的一个缺点   接受Iterable而不是Collection,就是你强迫   通过&#34;小管道#34;因此丢弃尺寸   可能有用的信息。如果您所有人都这样做,这很好   要做到的就是每一个,但如果你想做更多,如果你做得更好   可以保留您想要的所有信息。

     

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

<强>矛盾吗

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

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

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时移动了stream方法。

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

答案 2 :(得分: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()