为什么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>,确保最大容量,并提供了一些其他有用的功能。最好是直接作为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>?

?

35484 次浏览

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

到目前为止我还没有找到一个令人满意的解释。读完这些之后,我得出结论,这只是一个疏忽。但是你可以在这里看到,在API设计的这些年里,它被讨论了好几次。

Lambda lib规范专家

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

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

我和Brian一起工作,看看如何限制/子流 功能[1]可以实现,他建议转换为 迭代器才是正确的方法。我想过这个问题 解决方案,但没有找到任何明显的方法来接受迭代器并旋转 它变成了一条小溪。事实证明它就在那里,你只需要先 将迭代器转换为分离器,然后转换分离器 去一条小溪。这让我重新思考我们是否应该 这些直接挂在Iterable/Iterator中的一个或两个 我的建议是至少把它放在迭代器上,这样你就可以移动了 在两个世界之间,它也会很容易

< p > Streams.stream (Spliterators.spliteratorUnknownSize(迭代器, Spliterator.ORDERED) < / p >

然后布莱恩·戈茨回应道:

我认为Sam的观点是有很多图书馆的课程 给你一个迭代器,但不让你自己写 spliterator。所以你能做的就是打电话 流(spliteratorUnknownSize (iterator))。山姆建议我们 定义Iterator.stream()来为你做这件事。< / p >

我想保持stream()和spliterator()方法

And later

"鉴于编写Spliterator要比编写Iterator简单, 我更喜欢只写一个Spliterator而不是一个Iterator (Iterator是如此90年代:)" < / p > 不过,你没有抓住重点。外面有无数的课程 已经会给你一个迭代器。但他们中的许多人并不是 spliterator-ready . < / p >

Lambda邮件列表中的先前讨论

这可能不是你要找的答案,但在Project Lambda邮件列表中这被简要地讨论过。也许这有助于促进对这一主题进行更广泛的讨论。

用Brian Goetz在来自Iterable的流下的话来说:

退一步……

有很多方法来创建一个流。你得到的信息越多 有关于如何描述元素,更多的功能和 流库可以提供给您的性能。按最小到的顺序 大多数信息,它们是:

迭代器

迭代器+大小

Spliterator

分裂器,知道它的大小

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

(有些人可能会惊讶地发现我们甚至可以提取并行性 在Q(每个元素的功)为的情况下使用哑迭代器 非平凡。)< / p > 如果Iterable有一个stream()方法,它只会用 一个Spliterator,没有大小信息。但是,大多数事情都是 可迭代对象具有大小信息。也就是说我们要上菜了 缺乏流。这不是很好。

Stephen在这里概述的API实践的一个缺点是 接受Iterable而不是Collection,是你强制的 东西通过“小管子”因而丢弃大小 有用的信息。如果你所做的都是这样的话 如果你想做得更多,最好是你自己去做

Iterable提供的默认值确实是一个蹩脚的值——它 会丢弃大小,即使绝大多数的Iterables知道 这些信息。< / p >

矛盾吗?

虽然,看起来讨论是基于专家组对最初基于迭代器的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方法。

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

这不是疏忽;2013年6月有关于EG清单的详细讨论。

专家组的最终讨论是基于这个线程

虽然stream()Iterable上似乎是有意义的,这似乎是“显而易见的”(甚至对专家组来说,最初也是如此),但Iterable如此普遍的事实成为了一个问题,因为明显的签名:

Stream<T> stream()

并不总是你想要的。例如,一些Iterable<Integer>的东西宁愿让它们的流方法返回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将是一个错误。

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