170

我可以添加流或额外的元素,如下所示:

Stream stream = Stream.concat(stream1, Stream.concat(stream2, Stream.of(element));

我可以随时添加新内容,如下所示:

Stream stream = Stream.concat(
                       Stream.concat(
                              stream1.filter(x -> x!=0), stream2)
                              .filter(x -> x!=1),
                                  Stream.of(element))
                                  .filter(x -> x!=2);

但这很丑,因为concat是静态的。如果concat是一个实例方法,上面的例子会更容易阅读:

 Stream stream = stream1.concat(stream2).concat(element);

 Stream stream = stream1
                 .filter(x -> x!=0)
                 .concat(stream2)
                 .filter(x -> x!=1)
                 .concat(element)
                 .filter(x -> x!=2);

我的问题是:

1)有什么好的理由为什么concat是静态的?还是我缺少一些等效的实例方法?

2)无论如何,有没有更好的方法呢?

4

8 回答 8

170

不幸的是,这个答案可能几乎没有帮助,但我对 Java Lambda 邮件列表进行了取证分析,看看我是否能找到这种设计的原因。这是我发现的。

一开始有一个 Stream.concat(Stream) 的实例方法

在邮件列表中,我可以清楚地看到该方法最初是作为实例方法实现的,正如您可以在Paul Sandoz的这个线程中阅读的那样,关于 concat 操作。

在其中,他们讨论了流可能是无限的情况下可能出现的问题,以及在这些情况下串联意味着什么,但我认为这不是修改的原因。

您在另一个线程中看到,JDK 8 的一些早期用户质疑 concat 实例方法在与空参数一起使用时的行为。

但是,这个其他线程显示 concat 方法的设计正在讨论中。

重构为 Streams.concat(Stream,Stream)

但是没有任何解释,突然间,这些方法被更改为静态方法,正如您在这个线程中看到的关于组合流的内容。这可能是唯一一个对这种变化有所了解的邮件线程,但对于我来说还不够清楚,无法确定重构的原因。但是我们可以看到他们做了一个提交,他们建议将concat方法移出Stream和移入辅助类Streams

重构为 Stream.concat(Stream,Stream)

后来,它再次从移动StreamsStream,但又一次没有解释。

所以,最重要的是,设计的原因对我来说并不完全清楚,我找不到一个好的解释。我想您仍然可以在邮件列表中提出问题。

流连接的一些替代方案

Michael Hixson 的另一个线程讨论/询问了其他组合/连接流的方法

  1. 要结合两个流,我应该这样做:

    Stream.concat(s1, s2)
    

    不是这个:

    Stream.of(s1, s2).flatMap(x -> x)
    

    ... 正确的?

  2. 要组合两个以上的流,我应该这样做:

    Stream.of(s1, s2, s3, ...).flatMap(x -> x)
    

    不是这个:

    Stream.of(s1, s2, s3, ...).reduce(Stream.empty(), Stream::concat)
    

    ... 正确的?

于 2014-03-30T07:41:27.963 回答
128

如果为Stream.concatStream.of添加静态导入,第一个示例可以编写如下:

Stream<Foo> stream = concat(stream1, concat(stream2, of(element)));

使用通用名称导入静态方法会导致代码变得难以阅读和维护(命名空间污染)。因此,使用更有意义的名称创建自己的静态方法可能会更好。但是,为了演示,我将坚持使用这个名称。

public static <T> Stream<T> concat(Stream<? extends T> lhs, Stream<? extends T> rhs) {
    return Stream.concat(lhs, rhs);
}
public static <T> Stream<T> concat(Stream<? extends T> lhs, T rhs) {
    return Stream.concat(lhs, Stream.of(rhs));
}

使用这两个静态方法(可选地结合静态导入),这两个示例可以编写如下:

Stream<Foo> stream = concat(stream1, concat(stream2, element));

Stream<Foo> stream = concat(
                         concat(stream1.filter(x -> x!=0), stream2).filter(x -> x!=1),
                         element)
                     .filter(x -> x!=2);

代码现在显着缩短。但是,我同意可读性没有提高。所以我有另一个解决方案。


在很多情况下,收集器可用于扩展流的功能。底部有两个收集器,这两个例子可以写成如下:

Stream<Foo> stream = stream1.collect(concat(stream2)).collect(concat(element));

Stream<Foo> stream = stream1
                     .filter(x -> x!=0)
                     .collect(concat(stream2))
                     .filter(x -> x!=1)
                     .collect(concat(element))
                     .filter(x -> x!=2);

您所需的语法与上述语法之间的唯一区别是,您必须将concat(...)替换为collect(concat(...))。这两个静态方法可以实现如下(可选与静态导入结合使用):

private static <T,A,R,S> Collector<T,?,S> combine(Collector<T,A,R> collector, Function<? super R, ? extends S> function) {
    return Collector.of(
        collector.supplier(),
        collector.accumulator(),
        collector.combiner(),
        collector.finisher().andThen(function));
}
public static <T> Collector<T,?,Stream<T>> concat(Stream<? extends T> other) {
    return combine(Collectors.toList(),
        list -> Stream.concat(list.stream(), other));
}
public static <T> Collector<T,?,Stream<T>> concat(T element) {
    return concat(Stream.of(element));
}

当然,这个解决方案有一个缺点,应该提到。collect是消耗流的所有元素的最终操作。最重要的是,收集器concat每次在链中使用时都会创建一个中间ArrayList 。这两种操作都会对程序的行为产生重大影响。但是,如果可读性性能更重要,它可能仍然是一种非常有用的方法。

于 2014-03-30T08:04:14.310 回答
13

Just do:

Stream.of(stream1, stream2, Stream.of(element)).flatMap(identity());

where identity() is a static import of Function.identity().

Concatenating multiple streams into one stream is the same as flattening a stream.

However, unfortunately, for some reason there is no flatten() method on Stream, so you have to use flatMap() with the identity function.

于 2016-05-25T12:02:18.130 回答
12

我的StreamEx库扩展了 Stream API 的功能。特别是它提供了像appendprepend这样的方法来解决这个问题(在内部他们使用concat)。这些方法可以接受另一个流或集合或可变参数数组。使用我的库可以通过这种方式解决您的问题(请注意,x != 0对于非原始流看起来很奇怪):

Stream<Integer> stream = StreamEx.of(stream1)
             .filter(x -> !x.equals(0))
             .append(stream2)
             .filter(x -> !x.equals(1))
             .append(element)
             .filter(x -> !x.equals(2));

顺便说一句,您的filter操作还有一个快捷方式:

Stream<Integer> stream = StreamEx.of(stream1).without(0)
                                 .append(stream2).without(1)
                                 .append(element).without(2);
于 2015-08-11T12:31:19.727 回答
3

您可以使用 Guava 的方法,这会产生扁平流:Streams.concat(Stream<? extends T>... streams)

Stream stream = Streams.concat(stream1, stream2, Stream.of(element));
于 2017-09-18T10:30:18.307 回答
1

如果你不介意使用 3rd Party Libraries ,cyclops-react有一个扩展的 Stream 类型,它允许你通过 append / prepend 操作符来做到这一点。

单个值、数组、可迭代对象、流或反应流发布者可以作为实例方法附加和附加。

Stream stream = ReactiveSeq.of(1,2)
                           .filter(x -> x!=0)
                           .append(ReactiveSeq.of(3,4))
                           .filter(x -> x!=1)
                           .append(5)
                           .filter(x -> x!=2);

[披露我是 cyclops-react 的主要开发者]

于 2017-03-10T15:48:17.477 回答
0

编写自己的 concat 方法怎么样?

public static <T> Stream<T> concat(Stream<? extends T> a, 
                                   Stream<? extends T> b, 
                                   Stream<? extends T>... args)
{
    Stream<T> concatenated = Stream.concat(a, b);
    for (Stream<? extends T> stream : args)
    {
        concatenated = Stream.concat(concatenated, stream);
    }
    return concatenated;
}

这至少使您的第一个示例更具可读性。

正如@Legna 指出的那样,由于对 Stream::concat 的嵌套调用,这可能会很快导致 StackOverflowError。

所以这里有另一个版本应该可以解决这个问题并且看起来很整洁:

public static <T> Stream<T> concat(final Stream<? extends T>... args)
{
    return args == null ? Stream.empty()
                        : Stream.of(args).flatMap(Function.identity());
}
于 2017-08-16T12:06:58.717 回答
0

归根结底,我对组合流不感兴趣,而是对获得处理所有这些流的每个元素的组合结果感兴趣。

虽然组合流可能被证明很麻烦(因此这个线程),但组合它们的处理结果相当容易。

解决的关键是创建自己的收集器,并确保新收集器的供应商函数每次都返回相同的集合(不是新的),下面的代码说明了这种方法。

package scratchpad;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collector;
import java.util.stream.Stream;

public class CombineStreams {
    public CombineStreams() {
        super();
    }

    public static void main(String[] args) {
        List<String> resultList = new ArrayList<>();
        Collector<String, List<String>, List<String>> collector = Collector.of(
                () -> resultList,
                (list, item) -> {
                    list.add(item);
                },
                (llist, rlist) -> {
                    llist.addAll(rlist);
                    return llist;
                }
        );
        String searchString = "Wil";

        System.out.println("After processing first stream\n"
                + createFirstStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

        System.out.println("After processing second stream\n"
                + createSecondStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

        System.out.println("After processing third stream\n"
                + createThirdStream().filter(name -> name.contains(searchString)).collect(collector));
        System.out.println();

    }

    private static Stream<String> createFirstStream() {
        return Arrays.asList(
                "William Shakespeare",
                "Emily Dickinson",
                "H. P. Lovecraft",
                "Arthur Conan Doyle",
                "Leo Tolstoy",
                "Edgar Allan Poe",
                "Robert Ervin Howard",
                "Rabindranath Tagore",
                "Rudyard Kipling",
                "Seneca",
                "John Donne",
                "Sarah Williams",
                "Oscar Wilde",
                "Catullus",
                "Alfred Tennyson",
                "William Blake",
                "Charles Dickens",
                "John Keats",
                "Theodor Herzl"
        ).stream();
    }

    private static Stream<String> createSecondStream() {
        return Arrays.asList(
                "Percy Bysshe Shelley",
                "Ernest Hemingway",
                "Barack Obama",
                "Anton Chekhov",
                "Henry Wadsworth Longfellow",
                "Arthur Schopenhauer",
                "Jacob De Haas",
                "George Gordon Byron",
                "Jack London",
                "Robert Frost",
                "Abraham Lincoln",
                "O. Henry",
                "Ovid",
                "Robert Louis Stevenson",
                "John Masefield",
                "James Joyce",
                "Clark Ashton Smith",
                "Aristotle",
                "William Wordsworth",
                "Jane Austen"
        ).stream();
    }

    private static Stream<String> createThirdStream() {
        return Arrays.asList(
                "Niccolò Machiavelli",
                "Lewis Carroll",
                "Robert Burns",
                "Edgar Rice Burroughs",
                "Plato",
                "John Milton",
                "Ralph Waldo Emerson",
                "Margaret Thatcher",
                "Sylvie d'Avigdor",
                "Marcus Tullius Cicero",
                "Banjo Paterson",
                "Woodrow Wilson",
                "Walt Whitman",
                "Theodore Roosevelt",
                "Agatha Christie",
                "Ambrose Bierce",
                "Nikola Tesla",
                "Franz Kafka"
        ).stream();
    }
}
于 2019-03-15T16:17:10.780 回答