将两个Java 8流或一个额外元素添加到流中

时间:2014-03-30 05:03:02

标签: java concat java-8 java-stream

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

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)无论如何,有没有更好的方法呢?

8 个答案:

答案 0 :(得分:158)

不幸的是,这个答案可能很少或没有任何帮助,但我对Java Lambda Mailing列表进行了取证分析,看看我是否能找到这种设计的原因。这就是我发现的。

最初有一个Stream.concat(Stream)的实例方法

在邮件列表中,我可以清楚地看到该方法最初是作为实例方法实现的,正如您可以在Paul Sandoz的this thread中读到的关于concat操作的内容。

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

您在this other thread中看到JDK 8的一些早期用户在使用null参数时询问了concat实例方法的行为。

other thread显示,concat方法的设计正在讨论中。

重构为Streams.concat(Stream,Stream)

但是没有任何解释,突然之间,方法被改为静态方法,正如你在this thread about combining streams中看到的那样。这可能是唯一能够揭示这一变化的邮件主题,但我不清楚确定重构的原因。但我们可以看到他们did a commit他们建议将concat方法移出Stream并进入辅助类Streams

重构为Stream.concat(流,流)

稍后,it was moved againStreamsStream,但又一次,没有解释。

所以,底线,设计的原因对我来说并不完全清楚,我找不到一个好的解释。我想你仍然可以在邮件列表中提问。

流级联的一些替代方案

这个other thread by Michael Hixson讨论/询问关于组合/连接流的其他方法

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

    Stream.concat(s1, s2)
    
         

    不是这个:

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

    ......对吗?

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

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

    不是这个:

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

    ......对吗?

  4.   

答案 1 :(得分:122)

如果为 Stream.concat Stream.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 。这两种操作都会对程序的行为产生重大影响。但是,如果可读性性能更重要,那么它可能仍然是一种非常有用的方法。

答案 2 :(得分:12)

我的StreamEx库扩展了Stream API的功能。特别是它提供了解决此问题的appendprepend等方法(内部使用concat)。这些方法可以接受另一个流或集合或varargs数组。使用我的库你的问题可以通过这种方式解决(注意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);

答案 3 :(得分:9)

只是做:

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

其中identity()Function.identity()的静态导入。

将多个流连接成一个流与展平流相同。

但是,遗憾的是,由于某种原因,flatten()上没有Stream方法,因此您必须使用flatMap()标识函数。

答案 4 :(得分:2)

您可以使用Guava的Streams . concat(Stream<? extends T>... streams)方法,这对静态导入来说非常短暂:

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

答案 5 :(得分:1)

如果您不介意使用第三方库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);

[披露我是独眼巨人反应的主要开发者]

答案 6 :(得分:1)

最终,我对合并流不感兴趣,但是对获得处理所有这些流的每个元素的合并结果不感兴趣。

虽然合并流可能会很麻烦(因此该线程),但是合并它们的处理结果是相当合理的。

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

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();
    }
}

答案 7 :(得分:0)

如何编写自己的concat方法?

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

这至少使你的第一个例子更具可读性。