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

我想知道为什么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> ,确保最大的容量,并提供一些其他有用的function。 直接作为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:

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

所以到真正的问题:

  • 为什么Iterable<T>没有提供一个实现stream()parallelStream()的默认方法,我没有看到任何会使这个不可能或不需要的东西?

我发现一个相关的问题是: Stream <T>为什么不实现Iterable <T>?
奇怪的是,这足以让人反其道而行之。

这不是遗漏; EG细则在2013年6月份进行了详细的讨论。

专家组的权威性讨论植根于此 。

虽然它似乎“显而易见”(即使对于专家组来说,最初)该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:

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

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

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

到目前为止,我还没有find令人满意的解释。 看完这一切后,我断定这只是一个遗漏。 但是你可以在这里看到,在API的devise中多年来已经讨论过几次了。

Lambda Libs Spec专家

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

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

我正在和Brian一起研究如何实现限制/子streamfunction[1],他build议转换为Iterator是正确的方法。 我曾经想过这个解决scheme,但没有find任何明显的方式来把一个迭代器变成一个stream。 原来它在那里,你只需要首先将迭代器转换为分割器,然后将分割器转换为stream。 所以这就让我回想一下,是否应该把这些直接挂在Iterable / Iterator之一上,或者两者都是。

我的build议是至less在迭代器上做这个,所以你可以在两个世界之间干净地移动,而且它也很容易被发现而不必做:

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

然后Brian Goetz回应道 :

我认为Sam的观点是有很多库类给你一个迭代器,但是不要让你自己写一个分裂器。 所以你可以做的就是调用stream(spliteratorUnknownSize(iterator))。 山姆build议我们定义Iterator.stream()为你做。

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

然后

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

但是你错过了这一点。 那里已经有很多类已经给你一个迭代器了。 而且他们中的很多人都没有分裂准备。

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

这可能不是你正在寻找的答案,但在项目Lambda邮件列表中,这是简短的讨论。 也许这有助于就这个问题进行更广泛的讨论。

用布莱恩·戈茨(Brian Goetz)的“ 来自迭代的stream”(Streams from Iterable)

退后一步…

有很多方法来创build一个stream。 有关如何描述元素的更多信息,stream库可以提供的function和性能越多。 为了最less的信息,他们是:

迭代器

迭代器+大小

Spliterator

知道它的大小的Spliterator

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

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

如果Iterable有一个stream()方法,它将只包装一个带有Spliterator的Iterator,没有大小的信息。 但是,大多数Iterable的东西都有尺寸信息。 这意味着我们正在服务不足的stream。 那不太好。

Stephen在这里概述的接受Iterable而不是Collection的API实践的一个缺点是,你正在通过一个“小pipe道”强制事物,因此在可能有用的时候丢弃大小信息。 如果你所要做的只是为了做到这一点就好,但如果你想做得更多,如果你能保存所有你想要的信息,那么它会更好。

Iterable提供的默认值确实是一个糟糕的 – 即使绝大多数Iterables知道这些信息,它也会放弃大小。

矛盾?

尽pipe如此,讨论似乎是基于专家组对最初基于迭代器的Streams的初始devise所做的更改。

即使如此,有趣的是,在像Collection这样的接口中,stream方法被定义为:

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

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

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

重构的证据

继续在邮件列表中进行分析,看起来splitIterator方法最初是在Collection接口中,并且在2013年的某个时候,他们将其移动到了Iterable。

将SplitIterator从Collection集合拖曳到Iterable 。

结论/理论?

那么很可能在Iterable中缺less方法只是一个遗漏,因为当他们将SplitIterator从Collection移动到Iterable时,它们看起来应该已经移动了stream方法。

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

如果你知道这个大小,你可以使用提供stream()方法的java.util.Collection

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