【问题标题】:Adding two Java 8 streams, or an extra element to a stream向流中添加两个 Java 8 流或一个额外元素
【发布时间】:2014-03-30 05:03:01
【问题描述】:

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

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

【问题讨论】:

标签: java concat java-8 java-stream


【解决方案1】:

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

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

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

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

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

不过,other thread 表明 concat 方法的设计正在讨论中。

重构为 Streams.concat(Stream,Stream)

但是没有任何解释,突然间,方法被更改为静态方法,正如您在this thread about combining streams 中看到的那样。这可能是唯一一个对这种变化有所了解的邮件线程,但对于我来说还不够清楚,无法确定重构的原因。但是我们可以看到他们在did a commit 中建议将concat 方法从Stream 移出并移到帮助器类Streams 中。

重构为 Stream.concat(Stream,Stream)

后来,it was moved againStreamsStream,但又一次,没有对此作出解释。

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

流连接的一些替代方案

other thread by 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)
    

    ...对吗?

【讨论】:

  • +1 很好的研究。我将把它作为我的 Stream.concat 使用可变参数:public static <T> Stream<T> concat(Stream<T>... streams) { return Stream.of(streams).reduce(Stream.empty(), Stream::concat);}
  • 今天我写了自己的 concat 版本,之后我资助了这个话题。签名略有不同,但由于它更通用;)例如,您可以将 Stream 和 Stream 合并到 Stream.@SafeVarargs private static <T> Stream<T> concat(Stream<? extends T>... streams) { return Stream.of(streams).reduce(Stream.empty(),Stream::concat).map(Function.identity());}
  • @kant 为什么需要Function.identity() 地图?毕竟,它返回它收到的相同参数。这对结果流应该没有影响。我错过了什么吗?
  • 您是否尝试在 IDE 中键入它?没有 .map(identity()) 你会得到编译错误。我想返回 Stream 但声明:return Stream.of(streams).reduce(Stream.empty(),Stream::concat)返回 Stream extends T>.(Someting 是Something extends T> 的子类型,而不是其他方式,所以它不能被强制转换) 附加.map(identity()) cast 扩展到 。这要归功于方法参数的 java 8“目标类型”和返回类型以及 map() 方法的签名的混合。实际上是 Function.identity().
  • @kant 我认为? extends T 没有多大意义,因为您可以使用capture conversion。无论如何,这里是my gist code snippet 让我们在 Gist 中继续讨论。
【解决方案2】:

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

【讨论】:

  • 我觉得concat 收集器的可读性不高。像这样命名的单参数静态方法似乎很奇怪,并且使用collect 进行连接。
  • @nosid,也许与这个线程有点正交的问题,但你为什么声称It's a bad idea to import static methods with names?我真的很感兴趣——我发现它使代码更简洁易读,而且我问过的很多人都这么认为。愿意提供一些例子,为什么这通常是不好的?
  • @Quantum:compare(reverse(getType(42)), of(6 * 9).hashCode())是什么意思?请注意,我并没有说 静态导入 是一个坏主意,但是像 ofconcat 这样的通用名称的 静态导入 是。
  • @nosid:在现代 IDE 中将鼠标悬停在每个语句上不会很快揭示含义吗?无论如何,我认为这充其量可以说是个人偏好声明,因为我仍然认为“通用”名称的静态导入没有技术原因是不好的 - 除非您使用记事本或 VI(M) 进行编程,在这种情况下你有更大的问题。
  • 我不会说 Scala SDK 更好,但是……哎呀,我说过了。
【解决方案3】:

只要做:

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

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

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

但是,不幸的是,由于某种原因,Stream 上没有 flatten() 方法,因此您必须将 flatMap() 与标识函数一起使用。

【讨论】:

    【解决方案4】:

    我的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);
    

    【讨论】:

      【解决方案5】:

      您可以使用 Guava 的 Streams.concat(Stream&lt;? extends T&gt;... streams) 方法,这会导致扁平化流:

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

      【讨论】:

      • Stream.of 并不意味着静态导入。它被读作类似于Optional.of 的“流”,后者被读作“可选的”。您甚至无法在同一个文件中同时导入 Stream.ofOptional.of
      • @herman 你是对的 - 我修改了我的答案,专注于 Guava 带来的简洁性,并将静态导入的使用(或误用)留给读者自行决定。
      【解决方案6】:

      如果您不介意使用 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 的主要开发者]

      【讨论】:

        【解决方案7】:

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

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

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

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

        【讨论】:

          【解决方案8】:

          编写自己的 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());
          }
          

          【讨论】:

          • 从重复连接构造流时要小心。访问深度串联流的元素可能会导致深度调用链,甚至 StackOverflowError。
          猜你喜欢
          • 2015-04-19
          • 2023-04-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多