我想知道为什么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();
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个Hand的实现,因为你可以在玩卡片游戏时手中拿着牌.
基本上它包装a 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);
}
}
Run Code Online (Sandbox Code Playgroud)
现在你可以获得一个流:
Stream<Hand> stream = StreamSupport.stream(hand.spliterator(), false);
Run Code Online (Sandbox Code Playgroud)
所以关于真正的问题:
Iterable<T>
不提供实现一个默认的方法stream()
和parallelStream()
,我什么也看不到,这将使这个不可能的或不想要的?我发现的一个相关问题如下:为什么Stream <T>没有实现Iterable <T>?
奇怪的是,这表明它在某种程度上是相反的.
Bri*_*etz 288
这不是遗漏; 2013年6月对EG清单进行了详细讨论.
对专家组的最终讨论植根于这一主题.
虽然看似"显而易见"(即使对最初的专家组而言)stream()
似乎有意义Iterable
,但是Iterable
如此普遍的事实成为一个问题,因为明显的签名:
Stream<T> stream()
Run Code Online (Sandbox Code Playgroud)
并不总是你想要的.例如,有些东西Iterable<Integer>
宁愿让他们的流方法返回IntStream
.但是将这种stream()
方法放在层次结构中会使这种情况变得不可能.因此,相反,我们把它很容易使一个Stream
从Iterable
,通过提供一个spliterator()
方法.stream()
in 的实现Collection
只是:
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
Run Code Online (Sandbox Code Playgroud)
任何客户端都可以通过以下方式获取他们想要的流Iterable
:
Stream s = StreamSupport.stream(iter.spliterator(), false);
Run Code Online (Sandbox Code Playgroud)
最后,我们的结论是,加入stream()
到Iterable
将是一个错误.
Edw*_*rzo 23
我在几个项目lambda邮件列表中进行了调查,我想我发现了一些有趣的讨论.
到目前为止,我还没有找到令人满意的解释.阅读完所有这些后我得出的结论只是一个遗漏.但是你可以在这里看到,在API的设计过程中多年来已经多次讨论过它.
Lambda Libs Spec专家
我在Lambda Libs Spec Experts邮件列表中找到了关于此的讨论:
在Iterable/Iterator.stream()下, Sam Pullara说:
我正在与Brian一起研究如何实现限制/子流功能[1],他建议转换为Iterator是正确的方法.我曾考虑过这个解决方案,但没有找到任何明显的方法来获取迭代器并将其转换为流.事实证明它就在那里,你只需要先将迭代器转换为分裂器,然后将分裂器转换为流.所以这让我重新审视一下我们是否应该直接将Iterable/Iterator中的一个挂起或两者都挂掉.
我的建议是至少在迭代器上使用它,这样你就可以在两个世界之间干净利落地移动它也很容易被发现而不是必须这样做:
Streams.stream(Spliterators.spliteratorUnknownSize(iterator,Spliterator.ORDERED))
我认为Sam的观点是,有很多库类可以为您提供迭代器,但不要让您必须编写自己的分类器.所以你所能做的就是调用stream(spliteratorUnknownSize(iterator)).Sam建议我们定义Iterator.stream()来为你做这件事.
我想将stream()和spliterator()方法保留为库编写者/高级用户.
"鉴于编写Spliterator比编写Iterator更容易,我宁愿只编写一个Spliterator而不是Iterator(Iterator是90s :)"
但是你错过了这一点.有数以万计的课程已经给你一个迭代器.他们中的许多人都没有准备好分裂者.
以前在Lambda邮件列表中的讨论
这可能不是您正在寻找的答案,但在Project Lambda邮件列表中对此进行了简要讨论.也许这有助于促进对该主题的更广泛讨论.
用来自Iterable的Streams下的Brian Goetz的话来说:
退后一步......
有很多方法可以创建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);
}
Run Code Online (Sandbox Code Playgroud)
这可能与Iterable接口中使用的代码完全相同.
所以,这就是为什么我说这个答案可能不太令人满意,但仍然有趣的讨论.
重构的证据
继续在邮件列表中进行分析,看起来splitIterator方法最初位于Collection界面中,并且在2013年的某个时候,它们将其移动到Iterable.
将splitIterator从Collection转为Iterable.
结论/理论?
那么Iterable中缺少方法可能只是一个遗漏,因为看起来他们应该在将splitIterator从Collection移动到Iterable时移动了stream方法.
如果还有其他原因则不明显.别人有其他理论吗?
如果您知道可以使用的大小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();
}
}
Run Code Online (Sandbox Code Playgroud)
然后:
new Hand().stream().map(...)
Run Code Online (Sandbox Code Playgroud)
我遇到了同样的问题,并且很惊讶我的Iterable
实现可以AbstractCollection
通过简单地添加size()
方法很容易地扩展到实现(幸运的是我有收集的大小:-)
你还应该考虑覆盖Spliterator<E> spliterator()
.