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


168

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

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
看起来事情并不总是这样,但我只是找不到原因。
Edwin Dalorzo 2014年

Answers:


126

如果为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);

所需语法和上面语法之间的唯一区别是,您必须用collect(concat(...))替换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。两种操作都会对程序的行为产生重大影响。但是,如果可读性性能更重要,它可能仍然是非常有用的方法。


1
我发现concat收集器不易读。具有这样的单参数静态方法并collect用于串联似乎很奇怪。
Didier L

@nosid,也许对此线程有点正交的问题,但是为什么要声明It's a bad idea to import static methods with names呢?我真的很感兴趣-我发现它使代码更简洁易读,我问过的很多人都这么认为。小心提供一些例子,为什么这通常是不好的?
量子

1
@Quantum:什么意思compare(reverse(getType(42)), of(6 * 9).hashCode())?请注意,我并不是说静态导入是一个坏主意,但是对于诸如和的通用名称来说,静态导入是不可行的。ofconcat
nosid 2015年

1
@nosid:不会将鼠标悬停在现代IDE中的每个语句上会快速显示含义吗?无论如何,我认为这可以说是最好的个人喜好声明,因为我仍然看不到技术上为什么“通用”名称的静态导入不好的原因-除非在这种情况下使用Notepad或VI(M)进行编程你有更大的问题。
量子

我不会说Scala SDK更好,但是...哎呀,我说了。
eirirlar

165

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

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

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

在该文档中,他们讨论了流无限时产生的问题,以及级联在这些情况下意味着什么,但是我认为这不是进行修改的原因。

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

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

重构为Streams.concat(Stream,Stream)

但是突然之间,没有任何解释,方法变成了静态方法,正如您在本主题中看到的关于合并stream的那样。这也许是唯一可以了解此更改的邮件线程,但是对于我来说,尚不足以明确确定重构的原因。但是我们可以看到他们做了一次提交,他们建议将concat方法移出Stream辅助类Streams

重构为Stream.concat(Stream,Stream)

后来,它又Streams移到Stream,但再次没有解释。

因此,最重要的是,对于我来说,设计的原因尚不完全清楚,我找不到很好的解释。我想您仍然可以在邮件列表中提问。

流连接的一些替代方法

其他线程由迈克尔·希克森讨论/询问等方式来结合/ 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)

    ... 对?


6
+1不错的研究。我将使用它作为我的Stream.concat接受varargs:public static <T> Stream<T> concat(Stream<T>... streams) { return Stream.of(streams).reduce(Stream.empty(), Stream::concat);}
MarcG 2014年

1
今天,我编写了自己的concat版本,此后我就资助了这个主题。签名略有不同,但是要感谢它的通用性;)例如,您可以将Stream <Integer>和Stream <Double>合并到Stream <Number>。@SafeVarargs private static <T> Stream<T> concat(Stream<? extends T>... streams) { return Stream.of(streams).reduce(Stream.empty(),Stream::concat).map(Function.identity());}
康德2014年

@kant为什么需要Function.identity()地图?毕竟,它返回接收到的相同参数。这不会对结果流产生影响。我想念什么吗?
Edwin Dalorzo 2014年

1
您是否尝试过在IDE中键入它?没有.map(identity()),您将得到编译错误。我想返回Stream <T>但声明:return Stream.of(streams).reduce(Stream.empty(),Stream::concat)返回Stream <?扩展T>。(Someting <T>是Something <?扩展T>的子类型,不是其他方式,因此无法强制转换)附加强制转换.map(identity())<?将T>扩展到<T>。它的实现得益于方法参数的Java 8“目标类型”和返回类型以及map()方法签名的混合。实际上是Function。<T> identity()。
康德2014年

1
@kant我做的事情没有多大意义? extends T,因为您可以使用捕获转换。无论如何,这是我的要点代码片段。让我们继续要点中的讨论。
Edwin Dalorzo 2014年

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

9

做就是了:

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

identity()的静态导入在哪里Function.identity()

将多个流连接为一个流与拉平一个流相同。

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



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

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


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

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

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


1
从重复级联构造流时请谨慎。访问深度串联流的元素可能会导致深度调用链,甚至导致StackOverflowError。
莱格纳
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.