为什么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
很容易。 Collection
的stream()
的实现只是:
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()
。