2つの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年

回答:


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

これらの2つの静的メソッドを(オプションで静的インポートと組み合わせて)使用すると、2つの例は次のように書くことができます。

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

コードは大幅に短くなりました。ただし、読みやすさは改善されていません。だから私は別の解決策を持っています。


多くの状況で、コレクターを使用してストリームの機能を拡張できます。2つのコレクターが一番下にあるので、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(...))に置き換える必要があることです。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));
}

もちろん、このソリューションには言及すべき欠点があります。collectは、ストリームのすべての要素を消費する最後の操作です。さらに、コレクター連結は、チェーンで使用されるたびに中間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())何ですか?静的インポートが悪い考えだとは言っていませんが、一般的な名前の静的インポートof、やのようconcatです。
nosid

1
@nosid:最新のIDEで各文にカーソルを合わせると、すぐに意味がわかりませんか?いずれにせよ、私が「総称」名の静的インポートが悪いという技術的な理由はまだ見られないので、これは間違いなく個人の好みのステートメントである可能性があると思います。あなたはより大きな問題を抱えています。
クォンタム

Scala SDKの方が優れているとは言えませんが、おっと私は言った。
eirirlar 2018

165

残念ながら、この回答はおそらくほとんどまたはまったく役に立ちませんが、この設計の原因を見つけることができるかどうかを確認するために、Java Lambdaメーリングリストのフォレンジック分析を行いました。これは私が見つけたものです。

最初は、Stream.concat(Stream)のインスタンスメソッドがありました。

メーリングリストでは、concat操作について、Paul Sandoz がこのスレッドで読むことができるように、メソッドが最初はインスタンスメソッドとして実装されていたことがはっきりとわかります。

その中で、ストリームが無限である可能性がある場合に発生する可能性がある問題と、それらの場合に連結が何を意味するかについて説明しますが、それが変更の理由ではないと思います。

この別のスレッドで、JDK 8の初期のユーザーが、null引数とともに使用した場合のconcatインスタンスメソッドの動作について質問したことがわかります。

ただし、この別のスレッドは、concatメソッドの設計が検討中であったことを示しています。

Streams.concat(Stream、Stream)にリファクタリングされました

しかし、何の説明もなく、突然、このスレッドでストリームの結合について確認できるように、メソッドが静的メソッドに変更されました。これはおそらく、この変更について少し明かされた唯一のメールスレッドですが、リファクタリングの理由を判断するのに十分なほど明確ではありませんでした。しかし、彼らconcatメソッドをStreamヘルパークラスの外に移動するよう提案したコミットを実行したことがわかりStreamsます。

Stream.concat(Stream、Stream)にリファクタリング

その後、再びからに移動さStreamsましたStreamが、その説明はありません。

結論として、デザインの理由は私には完全に明確ではなく、良い説明が見つかりませんでした。メーリングリストで質問をしても構いません。

ストリーム連結のいくつかの代替手段

Michael Hixsonによるこの別のスレッドは、ストリームを結合/連結する他の方法について議論/質問します

  1. 2つのストリームを組み合わせるには、次のようにします。

    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)

    ... 正しい?


6
+1素晴らしい研究。そして、私は私のStream.concatは可変引数を取るとしてこれを使用する:public static <T> Stream<T> concat(Stream<T>... streams) { return Stream.of(streams).reduce(Stream.empty(), Stream::concat);}
MarcG

1
今日私は自分の連結バージョンを書きました、そしてその直後に私はこのトピックに資金を供給します。署名は少し異なりますが、そのおかげでより一般的です;)たとえば、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());}
カント

@kantなぜFunction.identity()地図が必要なのですか?結局のところ、受け取ったのと同じ引数を返します。これは、結果のストリームには影響しません。何か不足していますか?
Edwin Dalorzo

1
IDEに入力しようとしましたか?.map(identity())がないと、コンパイルエラーが発生します。Stream <T>を返したいが、ステートメント:return Stream.of(streams).reduce(Stream.empty(),Stream::concat)Stream <?T>を拡張します(Someting <T>はSomething <?extends T>のサブタイプであり、その逆ではないため、キャストできませんでした)追加の.map(identity())キャスト<?T>を<T>に拡張します。これは、Java 8のメソッド引数の「ターゲットタイプ」と戻りタイプおよびmap()メソッドのシグネチャの組み合わせのおかげで発生します。実際には、Function。<T> identity()です。
カント

1
@kant キャプチャ変換? extends Tを使用できるので、実行する意味があまりありません。とにかく、ここに私の要点コードスニペットがあります。要点での議論を続けましょう。
Edwin Dalorzo

12

私のStreamExライブラリは、Stream APIの機能を拡張します。特に、この問題を解決する追加プリペンドなどのメソッドを提供します(内部的にはを使用しています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()

複数のストリームを1つのストリームに連結することは、ストリームをフラット化することと同じです。

ただし、残念ながら、何らかの理由でにflatten()メソッドがないため、恒等関数Streamを使用する必要がありflatMap()ます。



1

サードパーティのライブラリを使用してもかまわない場合、cyclops-reactには拡張ストリームタイプがあり、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);

[開示私はc​​yclops-reactの主要開発者です]


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

独自の連結メソッドを作成してみませんか?

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が発生する可能性があります。
Legna
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.