问题
我想知道为什么theIterable
接口不提供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();
}
}
这是一个哈达斯的实现,你可以在玩卡片游戏时手中拿着牌。
基本上它包装aList<Card>
,确保最大容量并提供一些其他有用的功能。最好像aList<Card>
一样直接实现它。
现在,为了方便起见,我认为实现Iterable<Card>
会很好,这样如果你想循环它就可以使用增强的for循环。 (MyHand
class也提供了aget(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>?
奇怪的是,这表明它在某种程度上是相反的。
#1 热门回答(259 赞)
这不是遗漏; 2013年6月对EG清单进行了详细讨论。
对专家组的最终讨论植根于this thread。
虽然看起来"显而易见"(即使对专家组来说,最初)stream()
看起来在Iterable
上有意义,但52248889如此普遍的事实成了一个问题,因为明显的签名:
Stream<T> stream()
并不总是你想要的。例如,Iterable<Integer>
的某些东西宁愿让它们的流方法返回anIntStream
。但是将stream()
这样的方法放在层次结构中会使这种情况变得不可能。因此,通过提供aspliterator()
方法,我们可以很容易地从Iterable
制作aStream
。执行stream()
inCollection
只是:
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
任何客户端都可以从aIterable
获取他们想要的流:
Stream s = StreamSupport.stream(iter.spliterator(), false);
最后我们得出结论,添加stream()
至Iterable
将是一个错误。
#2 热门回答(20 赞)
我在几个项目lambda邮件列表中进行了调查,我想我发现了一些有趣的讨论。
到目前为止,我还没有找到令人满意的解释。读完这一切之后我得出结论,这只是一个遗漏。但是你可以在这里看到,在API的设计过程中多年来已经多次讨论过它。
Lambda Libs Spec Experts
我在Lambda Libs Spec Experts mailing list找到了关于这个问题的讨论:
UnderIterable/Iterator.stream()Sam Pullara说:
我正在与Brian合作,看看如何实现限制/子流功能[1],他建议转换为Iterator是正确的方法。我曾考虑过这个解决方案,但没有找到任何明显的方法来获取迭代器并将其转换为流。事实证明它就在那里,你只需要首先将迭代器转换为分裂器,然后将分裂器转换为流。因此,这让我重新审视我们是否应该直接将Iterable / Iterator中的一个挂起或两者都挂掉。我的建议是至少在Iterator上有它,这样你就可以在两个世界之间干净利落地移动它也很容易被发现而不必去做:Streams.stream(Spliterators.spliteratorUnknownSize(iterator,Spliterator.ORDERED))
我认为Sam的观点是,有很多库类可以为你提供迭代器,但不要让你必须编写自己的分类器。所以你所能做的就是调用stream(spliteratorUnknownSize(iterator))。 Sam建议我们定义Iterator.stream()来为你做这件事。我想将stream()和spliterator()方法保留为库编写者/高级用户。
And later>"鉴于编写Spliterator比编写Iterator更容易,我宁愿只编写一个Spliterator而不是Iterator(Iterator就是90s :)"但你错过了这一点。有数以万计的课程已经给你一个迭代器。他们中的许多人并不是分裂者准备好的。
以前的讨论在Lambda邮件列表
这可能不是你正在寻找的答案,但在Project Lambda mailing list中对此进行了简要讨论。也许这有助于促进关于这一主题的更广泛的讨论。
用Brian Goetz所说的Streams from Iterable:
退一步......有很多方法可以创建一个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);
}
这可能与Iterable接口中使用的代码完全相同。
所以,这就是为什么我说这个答案可能并不令人满意,但仍然对讨论很有意思。
重构证据
继续在邮件列表中进行分析,看起来splitIterator方法最初位于Collection界面中,并且在2013年的某些时候,它们将其移动到Iterable。
Pull splitIterator up from Collection to Iterable。
结论/理论?
那么Iterable中缺少方法可能只是一个遗漏,因为看起来他们应该在将splitIterator从Collection移动到Iterable时移动了stream方法。
如果还有其他原因则不明显。别人有其他理论吗?
#3 热门回答(4 赞)
如果你知道尺寸,可以使用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(...)
我遇到了同样的问题,并且很惊讶myIterable
实现可以通过简单地添加size()
方法非常容易地扩展到AbstractCollection
实现(幸运的是我有收集的大小:-)
你还应该考虑覆盖346868589。