我想知道为什么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>? 奇怪的是,这暗示着用另一种方式来做。


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

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

Lambda lib规范专家

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

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

I was working with Brian on seeing how limit/substream functionality[1] might be implemented and he suggested conversion to Iterator was the right way to go about it. I had thought about that solution but didn't find any obvious way to take an iterator and turn it into a stream. It turns out it is in there, you just need to first convert the iterator to a spliterator and then convert the spliterator to a stream. So this brings me to revisit the whether we should have these hanging off one of Iterable/Iterator directly or both. My suggestion is to at least have it on Iterator so you can move cleanly between the two worlds and it would also be easily discoverable rather than having to do: Streams.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED))

然后Brian Goetz回应道:

我想山姆的意思是有很多图书馆的课程 给你一个迭代器,但不让你自己写 spliterator。所以你能做的就是打电话 流(spliteratorUnknownSize (iterator))。山姆建议我们 定义Iterator.stream()来为你做这件事。 我想保留stream()和spliterator()方法 适用于库作者/高级用户。

后来

"鉴于编写Spliterator要比编写Iterator简单, 我宁愿只写一个Spliterator而不是一个Iterator (Iterator太90年代了:)” 不过,你没有抓住重点。外面有无数的课程 那里已经给了你一个迭代器。但他们中的许多人并不是 spliterator-ready。

Lambda邮件列表中的先前讨论

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

用Brian Goetz在《Streams from Iterable》中的话说:

Stepping back... There are lots of ways to create a Stream. The more information you have about how to describe the elements, the more functionality and performance the streams library can give you. In order of least to most information, they are: Iterator Iterator + size Spliterator Spliterator that knows its size Spliterator that knows its size, and further knows that all sub-splits know their size. (Some may be surprised to find that we can extract parallelism even from a dumb iterator in cases where Q (work per element) is nontrivial.) If Iterable had a stream() method, it would just wrap an Iterator with a Spliterator, with no size information. But, most things that are Iterable do have size information. Which means we're serving up deficient streams. That's not so good. One downside of the API practice outlined by Stephen here, of accepting Iterable instead of Collection, is that you are forcing things through a "small pipe" and therefore discarding size information when it might be useful. That's fine if all you're doing to do is forEach it, but if you want to do more, its better if you can preserve all the information you want. The default provided by Iterable would be a crappy one indeed -- it would discard size even though the vast majority of Iterables do know that information.

矛盾吗?

虽然,看起来讨论是基于专家组对最初基于迭代器的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>的东西宁愿让它们的stream方法返回IntStream。但是将stream()方法放在层次结构中如此高的位置将使这成为不可能。因此,我们通过提供spliterator()方法,使从Iterable生成Stream变得非常容易。stream()在Collection中的实现就是:

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实现可以通过添加size()方法很容易地扩展到AbstractCollection实现(幸运的是我有集合的大小:-)

您还应该考虑重写Spliterator<E> Spliterator()。