向一个流添加两个 Java 8 流或一个额外元素

2022-08-31 06:56:35

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

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

但这是丑陋的,因为这是静态的。如果是实例方法,上面的示例将更容易阅读:concatconcat

 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)无论如何,有没有更好的方法来做到这一点?


答案 1

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

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

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

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

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

不过,另一个线程显示,concat 方法的设计正在讨论中。

重构为 Streams.concat(Stream,Stream)

但是没有任何解释,突然之间,这些方法被更改为静态方法,正如您在有关组合流的线程中看到的那样。这也许是唯一一个阐明此更改的邮件线程,但是对于我来说,它不够清楚,无法确定重构的原因。但是我们可以看到他们做了一个提交,其中他们建议将方法移出并移入帮助器类 。concatStreamStreams

重构为 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)
    

    ...右?


答案 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 是使用流的所有元素的最终操作。最重要的是,每次在链中使用中间 ArrayList 时,收集器 concat 都会创建一个中间 ArrayList。这两种操作都会对程序的行为产生重大影响。但是,如果可读性性能更重要,它可能仍然是一种非常有用的方法。


推荐