向流中添加两个 Java8流或一个额外的元素

我可以添加流或其他元素,如下所示:

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

118369 次浏览

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

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

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

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

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

但是,这个 另一条线揭示了 concat 方法的设计正在讨论之中。

重构为 Streams.concat (Stream,Stream)

但是在没有任何解释的情况下,这些方法突然变成了静态方法,正如您在 这个关于合并流的线程中看到的那样。这可能是唯一一个能够透露一些有关此更改的邮件线程,但是对于我来说,它还不够清楚,无法确定重构的原因。但是我们可以看到它们是 犯了罪,它们建议将 concat方法从 Stream移到助手类 Streams中。

重构为 Stream.concat (Stream,Stream)

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

所以,底线,设计的原因对我来说并不完全清楚,我找不到一个很好的解释。我想你还是可以在邮件列表里问这个问题。

流级联的几种选择

这个 Michael Hixson 的另一个帖子讨论/询问组合/concat 流的其他方法

  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)
    

    对吧?

如果为 Stream concat添加 静态进口,第一个示例可以编写如下:

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

您所需的语法和上面的语法之间的唯一区别是,您必须用 收集(...)替换 连接(...)。这两个静态方法可以按如下方式实现(可选地与静态导入结合使用) :

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

当然,这个解决方案有一个缺点,应该被提及。收钱是使用流的所有元素的最终操作。除此之外,收集器 连接每次在链中使用时都会创建一个中间 数组列表。这两个操作都可能对程序的行为产生重大影响。然而,如果 可读性表演更重要,它可能仍然是一个非常有用的方法。

我的 StreamEx库扩展了 StreamAPI 的功能。特别是它提供了像 附录假设这样的方法来解决这个问题(在内部他们使用 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);

只要做:

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

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

将多个流连接到一个流中与压平一个流相同。

但是,不幸的是,由于某些原因,在 Stream上没有 flatten()方法,因此必须使用带标识函数的 flatMap()

如果您不介意使用第三方库 剑水蚤-反应有一个扩展的 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);

[披露我是剑水蚤反应的首席开发者]

写一个你自己的 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 的嵌套调用,这可能很快导致 StackOverflow 错误。

因此,这里有另一个版本可以解决这个问题,看起来相当简洁:

public static <T> Stream<T> concat(final Stream<? extends T>... args)
{
return args == null ? Stream.empty()
: Stream.of(args).flatMap(Function.identity());
}

你可以使用番石榴的 Streams.concat(Stream<? extends T>... streams)方法,它会产生一个扁平的流:

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

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

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

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

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