默认的Stream<E> stream()与静态的<T> Stream<T> of(T t)有什么区别?

4
在Java 8中,Collection接口添加了default Stream<E> stream()方法以支持流。Stream类中的public static<T> Stream<T> of(T t)方法支持类似的功能。静态of()方法解决了什么不同的目的?

1
您可以使用varargs和数组创建Stream,但不能直接通过Collection方式创建。 - luk2302
4个回答

6
Collection接口的stream()方法可以在已有的集合上调用。作为接口方法,它可以被实际的集合实现所覆盖,以返回适配于特定集合类型的Stream
JRE的标准集合不利用这个机会,因为默认实现委托给了spliterator(),它也是一个可重写的方法,这种策略适合它们的需求。但文档甚至提到了集合应该覆盖stream()的场景:

spliterator()方法无法返回IMMUTABLECONCURRENTlate-binding分离器时,应该覆盖此方法。


相比之下,static 工厂方法 Stream.of(…) 是为了在没有特定集合的情况下拥有固定数量元素的情况而设计的。当你只想枚举元素并获得单个 Stream 时,无需创建临时 Collection。没有不同类型的可能性集合,因此不需要可重写的行为,因此一个 static 工厂方法就足够了。
请注意,即使您没有一个可枚举的固定元素数量,当不需要可重复使用的集合时,创建单个流的任务也有一种优化解决方案:
Stream.Builder<MyType> builder=Stream.builder();
builder.add(A);
if(condition) builder.add(B);
builder.add(C);
builder.build()./* stream operations */

首先,点个赞。但是 Collection.streamStream.of 的主要区别在于,前者应用了模板方法模式,而后者应用了工厂方法模式。它们都是工厂方法。我说的对吗? - holi-java
Collection.stream 也可以被认为是实现了抽象工厂模式(即使有默认值)。在软件中识别出多个模式并不罕见。您可以使用术语静态工厂方法作为Stream.of方法背后的模式名称来限定它。我甚至在我的回答中无意中这样做了。我通常更喜欢关注设计的含义,而不是软件设计模式的名称。 - Holger
谢谢。我认为你的评论比任何答案都更好。 - holi-java

3
据我所知,of 只是一种实用方法,可以即时创建流对象,无需首先将元素封装在集合中。
通常静态工厂方法,如 of,被提供为跳过数组创建的可变参数而设计。例如,Java 9 中的不可变集合提供了许多这些方法的重载形式:
 Set.of()
 Set.of(E)
 Set.of(E e1, E e2)
 .... so on until 11
 Set.of(E... elem)

即使是这些方法的描述也是:

虽然这样会在API中增加一些混乱,但它避免了可变参数调用引起的数组分配、初始化和垃圾回收开销。

因为Stream只有两种方法:

 Stream.of(T t)
 Streamm.of(T ... values)

我认为可以使用小型工具方法来从var-args创建流。

但是它们仍然提供了一种创建单个元素流的方法(而不是只留下var-args方法),因此对于单个元素,这已经被优化了。


Stream.of 方法没有像 Set.ofList.of 那样有很多的原因是后者保证返回一个不可变的集合,因此不能仅仅保留对可能被共享的数组的引用。相反,Stream.of(T...) 只是委托给 Arrays.stream,所以生成的流将在不进行其他复制步骤的情况下遍历数组。 - Holger

2
一个接口可以在JDK-8及以后版本中添加静态方法/默认方法。你可以在this question中看到一些将模式应用于接口的例子。 首先,它们都调用StreamSupport.stream来创建流。
// Collection.stream()
default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

// Stream.of(E)
public static<T> Stream<T> of(T t) {
    return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}

Collection接口中添加的stream()是在默认方法中应用模板方法模式的一个很好的例子。你可以看到源代码,stream方法调用了一个在Collection接口中默认实现的方法spliterator。
default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

default Spliterator<E> spliterator() {
    return Spliterators.spliterator(this, 0);
}

继承自Collection

AND类可以重写spliterator,实现使用最高性能算法的不同算法。例如ArrayList中的spliterator

public Spliterator<E> spliterator() {
    return new ArrayListSpliterator<>(this, 0, -1, 0);
}

最后,Stream.of()方法是一个很好的例子,通过静态方法在接口中应用工厂方法。它是一个从对象实例创建流的工厂方法。

2
其他答案已经清楚地解释了Collection.streamStream.of之间的区别,何时使用其中一个,应用了哪些设计模式等。@Holger甚至进一步展示了Stream.Builder的使用示例,我认为这是高度被低估的。
在这里,我想通过展示混合使用Stream.ofCollection.stream方法来补充其他答案。我希望这个示例足够清晰,以表明即使Stream.ofCollection.stream是完全不同的方法,它们也可以一起使用来实现更复杂的需求。
假设你有N个列表,它们都包含相同类型的元素:
List<A> list1 = ...;
List<A> list2 = ...;
...
List<A> listN = ...;

您想创建一个包含所有列表元素的流。

您可以创建一个新的空列表,并将所有列表的元素添加到该新列表中:

int newListSize = list1.size() + list2.size() + ... + listN.size();
List<A> newList = new ArrayList<>(newListSize);

newList.addAll(list1);
newList.addAll(list2);
...
newList.addAll(listN);

然后,您可以在此列表上调用stream()并完成操作:
Stream<A> stream = newList.stream();

然而,您将会创建一个中间的无意义列表,其唯一目的是流式传输原始的list1list2、...、listN列表元素。

更好的方法是使用Stream.of

Stream<A> stream = Stream.of(list1, list2, ..., listN)
    .flatMap(Collection::stream);

首先,通过枚举每个列表来创建一个列表流,然后使用 Stream.flatMap 操作将此列表流展开为所有列表元素的流。因此,在展开原始流时调用了 Collection.stream


首先,点赞。将集合流组合起来的好习惯是使用Stream.concat(...streams),但也可以使用它。 - holi-java
1
经过思考,我认为你的方法是实现这个目标的好方法。干得好!编写一个组合方法是不必要的,并且使用 Stream.of(...Collection).flatMap(Collection::stream) 会产生重复的逻辑。 - holi-java

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接