サーチ…


前書き

Streamは要素のシーケンスを表し、それらの要素の計算を実行するための異なる種類の操作をサポートします。 Java 8では、 CollectionインタフェースにStreamを生成する2つのメソッドstream()parallelStream()ます。 Stream操作は、中間または端末のいずれかです。中間操作はStream返すので、 Streamが閉じる前に複数の中間操作をチェーンすることができます。ターミナル操作は無効であるか、非ストリーム結果を返します。

構文

  • collection.stream()
  • 配列。ストリーム(配列)
  • Stream.iterate(firstValue、currentValue - > nextValue)
  • Stream.generate(() - > value)
  • Stream.of(elementOfT [、elementOfT、...])
  • Stream.empty()
  • StreamSupport.stream(iterable.spliterator()、false)

ストリームの使用

Streamとは、順次および並列の集約操作を実行できる一連の要素です。任意のStreamは、データが無制限に流れる可能性があります。その結果、 Streamから受信したデータは、データのバッチ処理を実行するのではなく、到着時に個別に処理されます。 ラムダ式と組み合わせると、機能的アプローチを使用して一連のデータに対する操作を簡潔に行うことができます。

例:( それはIdeoneで動作することを参照してください

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");

fruitStream.filter(s -> s.contains("a"))
           .map(String::toUpperCase)
           .sorted()
           .forEach(System.out::println);

出力:

林檎
バナナ
オレンジ

上記のコードで実行される操作は、次のように要約できます。

  1. Stream<String>ファクトリメソッドStream.of(values)を使用して、シーケンスされた順序付けられたフルーツString要素のStreamを含むStream<String>を作成します。

  2. filter()オペレーションは、与えられた述語(述語によってテストされたときにtrueを返す要素)と一致する要素だけを保持します。この場合、 "a"を含む要素は保持されます。述語はラムダ式として与えられます

  3. map()操作は、マッパーと呼ばれる特定の関数を使用して各要素を変換します。この場合、各フルーツStringは、 メソッド参照 String::toUppercaseを使用して、大文字のStringバージョンにマップされます。

    ことに留意されたい map()マッピング関数は、入力パラメータに異なるタイプを返した場合、操作は異なる一般的なタイプのストリームを返します。たとえば、 Stream<String>.map(String::isEmpty)呼び出すと、 Stream<Boolean>

  4. sorted()オペレーションは、自然順序付け( Stringの場合は辞書編集的にsorted()に従って、 Streamの要素をソートします。

  5. 最後に、 forEach(action)操作は、 Stream各要素に対して動作し、 Consumerに渡すアクションを実行します。この例では、各要素は単にコンソールに印刷されています。この操作は端末操作であり、再度操作することはできません。

    Stream定義された操作は、端末操作のために実行されること注意しください 。ターミナル操作なしでは、ストリームは処理されません。ストリームは再利用できません。ターミナル操作が呼び出されると、 Streamオブジェクトは使用できなくなります。

連鎖操作

操作は(上記のように)連鎖されて、データに対するクエリとして見えるものを形成します。


ストリームを閉じる

Stream一般的に閉じておく必要はないことに注意してください。 IOチャネル上で動作するストリームを閉じる必要があるだけです。ほとんどのStreamタイプはリソース上で動作しないため、終了する必要はありません。

StreamインターフェイスはAutoCloseable拡張しAutoCloseable 。ストリームは、 closeメソッドを呼び出すか、try-with-resourceステートメントを使用してcloseことができます。

Streamを閉じる必要があるユースケースの例は、ファイルから行のStreamを作成する場合です。

try (Stream<String> lines = Files.lines(Paths.get("somePath"))) {
    lines.forEach(System.out::println);
}

Streamインターフェイスは、 Stream.onClose()メソッドも宣言します。このメソッドを使用すると、ストリームが閉じられたときに呼び出されるRunnableハンドラを登録できます。ストリームを生成するコードが何らかのクリーンアップを実行するために消費されたときを知る必要がある場合の使用例があります。

public Stream<String>streamAndDelete(Path path) throws IOException {
    return Files.lines(path).onClose(() -> someClass.deletePath(path));
}

実行ハンドラは、try-with-resourcesステートメントによって明示的または暗黙的にclose()メソッドが呼び出された場合にのみ実行されます。


注文の処理

Streamオブジェクトの処理は、シーケンシャルまたはパラレルで行うことができます。

シーケンシャルモードでは、要素はStreamソースの順に処理されます。 Streamが順序付けされている場合( SortedMap実装やList )、処理はソースの順序に一致することが保証されます。しかし、他のケースでは、順序に依存しないように注意する必要がありますkeySet() Java HashMap keySet()反復順序は一貫していますか?を参照してください )。

例:

List<Integer> integerList = Arrays.asList(0, 1, 2, 3, 42); 

// sequential 
long howManyOddNumbers = integerList.stream()
                                    .filter(e -> (e % 2) == 1)
                                    .count(); 

System.out.println(howManyOddNumbers); // Output: 2

イデオンに生きる

パラレルモードでは、複数のコアで複数のスレッドを使用できますが、要素の処理順序を保証するものではありません。

複数のメソッドが順次Streamで呼び出された場合、すべてのメソッドを呼び出す必要はありません。たとえば、 Streamがフィルタリングされ、要素の数が1に減らされた場合、 sortなどのメソッドへの後続の呼び出しは行われません。これにより、順次Streamパフォーマンスが向上する可能性がありStream 。これは、並列Streamでは不可能な最適化です。

例:

// parallel
long howManyOddNumbersParallel = integerList.parallelStream()
                                            .filter(e -> (e % 2) == 1)
                                            .count();

System.out.println(howManyOddNumbersParallel); // Output: 2

イデオンに生きる


コンテナ(またはコレクション )との違い

いくつかのアクションはコンテナとストリームの両方で実行できますが、最終的には異なる目的を果たし、さまざまな操作をサポートします。コンテナは、要素がどのように格納されているか、それらの要素に効率的にアクセスする方法に焦点が当てられています。一方、 Streamは要素への直接アクセスと操作を提供しません。それは集合的エンティティとしてのオブジェクトのグループに専念し、そのエンティティ全体に対して操作を実行する。 StreamCollectionは、これらの異なる目的のための別個の高レベル抽象である。

コレクションの要素をコレクションに集める

toList()toSet()収集する

Stream要素は、 Stream.collect操作を使用してコンテナに簡単に収集できます。

System.out.println(Arrays
    .asList("apple", "banana", "pear", "kiwi", "orange")
    .stream()
    .filter(s -> s.contains("a"))
    .collect(Collectors.toList())
);
// prints: [apple, banana, pear, orange]

Setなどの他のコレクションインスタンスは、他のCollectors組み込みメソッドを使用して作成できます。たとえば、 Collectors.toSet()Streamの要素をSet集めます。


ListまたはSet実装の明示的な制御

Collectors#toList()Collectors#toSet()ドキュメントによれば、返されるListまたはSetの型、変更可能性、直列化可能性、またはスレッド安全性についての保証はありません。

返される実装の明示的な制御のために、代わりに指定されたサプライヤが新しい空のコレクションを返すCollectors#toCollection(Supplier)を使用できます。

// syntax with method reference
System.out.println(strings
        .stream()
        .filter(s -> s != null && s.length() <= 3)
        .collect(Collectors.toCollection(ArrayList::new))
);

// syntax with lambda
System.out.println(strings
        .stream()
        .filter(s -> s != null && s.length() <= 3)
        .collect(Collectors.toCollection(() -> new LinkedHashSet<>()))
);

toMapを使用した要素の収集

コレクターは要素をMapに蓄積します。ここで、keyは生徒IDであり、Valueは生徒値です。

  List<Student> students = new ArrayList<Student>(); 
    students.add(new Student(1,"test1"));
    students.add(new Student(2,"test2"));
    students.add(new Student(3,"test3"));
    
    Map<Integer, String> IdToName = students.stream()
        .collect(Collectors.toMap(Student::getId, Student::getName));
    System.out.println(IdToName);

出力:

{1=test1, 2=test2, 3=test3}

Collector.toMapにはCollector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) 。mergeFunctionは、リストからマップに新しいメンバを追加するときにキーが繰り返される場合、主に新しい値を選択するか古い値を保持するために使用されます。

mergeFunctionは、 (s1, s2) -> s1ように繰り返しキーに対応する値を保持するか、 (s1, s2) -> s2繰り返しキーの新しい値を設定します。

コレクションの要素を収集する

例:ArrayListからMapへ<String、List <>>

しばしば、プライマリリストからリストのマップを作成する必要があります。例:リストの学生から、各学生の科目リストを作成する必要があります。

    List<Student> list = new ArrayList<>();
    list.add(new Student("Davis", SUBJECT.MATH, 35.0));
    list.add(new Student("Davis", SUBJECT.SCIENCE, 12.9));
    list.add(new Student("Davis", SUBJECT.GEOGRAPHY, 37.0));

    list.add(new Student("Sascha", SUBJECT.ENGLISH, 85.0));
    list.add(new Student("Sascha", SUBJECT.MATH, 80.0));
    list.add(new Student("Sascha", SUBJECT.SCIENCE, 12.0));
    list.add(new Student("Sascha", SUBJECT.LITERATURE, 50.0));

    list.add(new Student("Robert", SUBJECT.LITERATURE, 12.0));

    Map<String, List<SUBJECT>> map = new HashMap<>();
    list.stream().forEach(s -> {
                map.computeIfAbsent(s.getName(), x -> new ArrayList<>()).add(s.getSubject());
            });
    System.out.println(map);

出力:

{ Robert=[LITERATURE], 
Sascha=[ENGLISH, MATH, SCIENCE, LITERATURE], 
Davis=[MATH, SCIENCE, GEOGRAPHY] }

例:ArrayListからMap <String、Map <>>

    List<Student> list = new ArrayList<>();
    list.add(new Student("Davis", SUBJECT.MATH, 1, 35.0));
    list.add(new Student("Davis", SUBJECT.SCIENCE, 2, 12.9));
    list.add(new Student("Davis", SUBJECT.MATH, 3, 37.0));
    list.add(new Student("Davis", SUBJECT.SCIENCE, 4, 37.0));

    list.add(new Student("Sascha", SUBJECT.ENGLISH, 5, 85.0));
    list.add(new Student("Sascha", SUBJECT.MATH, 1, 80.0));
    list.add(new Student("Sascha", SUBJECT.ENGLISH, 6, 12.0));
    list.add(new Student("Sascha", SUBJECT.MATH, 3, 50.0));

    list.add(new Student("Robert", SUBJECT.ENGLISH, 5, 12.0));

    Map<String, Map<SUBJECT, List<Double>>> map = new HashMap<>();

    list.stream().forEach(student -> {
        map.computeIfAbsent(student.getName(), s -> new HashMap<>())
                .computeIfAbsent(student.getSubject(), s -> new ArrayList<>())
                .add(student.getMarks());
    });

    System.out.println(map);

出力:

{ Robert={ENGLISH=[12.0]}, 
Sascha={MATH=[80.0, 50.0], ENGLISH=[85.0, 12.0]}, 
Davis={MATH=[35.0, 37.0], SCIENCE=[12.9, 37.0]} }

カンニングペーパー

ゴールコード
List集める Collectors.toList()
事前割り当てサイズのArrayList収集する Collectors.toCollection(() -> new ArrayList<>(size))
Set集める Collectors.toSet()
より良い反復性能でSet集める Collectors.toCollection(() -> new LinkedHashSet<>())
大文字と小文字を区別しないSet<String> Collectors.toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER))
EnumSet<AnEnum>集める(列挙型で最高のパフォーマンス) Collectors.toCollection(() -> EnumSet.noneOf(AnEnum.class))
一意のキーを使用してMap<K,V>収集する Collectors.toMap(keyFunc,valFunc)
MyObject.getter()を一意のMyObjectにマップする Collectors.toMap(MyObject::getter, Function.identity())
MyObject.getter()を複数のMyObjectにマップする Collectors.groupingBy(MyObject::getter)

無限の流れ

終了しないStreamを生成することは可能です。無限Stream上で端末メソッドを呼び出すと、 Streamは無限ループに入ります。 Streamlimitメソッドは、Javaが処理するStreamの用語の数を制限するために使用できます。

この例では、発生するStreamの各連続ターム番号1から始まる、すべての自然数のをStream以前より高い方です。このStream limitメソッドを呼び出すことで、 Stream最初の5つの項だけが考慮され、出力されます。

// Generate infinite stream - 1, 2, 3, 4, 5, 6, 7, ...
IntStream naturalNumbers = IntStream.iterate(1, x -> x + 1);

// Print out only the first 5 terms
naturalNumbers.limit(5).forEach(System.out::println);

出力:

1
2
3
4
5


無限ストリームを生成する別の方法は、 Stream.generateメソッドを使用することです。このメソッドは、 Supplier型のラムダを取ります。

// Generate an infinite stream of random numbers
Stream<Double> infiniteRandomNumbers = Stream.generate(Math::random);

// Print out only the first 10 random numbers
infiniteRandomNumbers.limit(10).forEach(System.out::println);

ストリームを消費する

Streamは、 count()collect()forEach()などの端末操作がある場合にのみトラバースされます。それ以外の場合は、 Streamに対する操作は実行されません。

次の例では、ターミナル操作はStreamに追加されないため、 peek()ターミナル操作ではないため、 filter()操作は呼び出されず、出力も生成されません。

IntStream.range(1, 10).filter(a -> a % 2 == 0).peek(System.out::println);

イデオンに生きる

これは、有効なターミナル操作を持つStreamシーケンスであるため、出力が生成されます。

peek代わりにforEachを使うこともできます:

IntStream.range(1, 10).filter(a -> a % 2 == 0).forEach(System.out::println); 

イデオンに生きる

出力:

2
4
6
8

ターミナル操作が実行された後、 Streamは消費され、再利用することはできません。


ストリームオブジェクトを再利用することはできませんが、ストリームパイプラインに委譲する再利用可能なIterableを作成するのは簡単です。これは、一時的な構造に結果を収集することなく、ライブデータセットの変更されたビューを返すのに便利です。

List<String> list = Arrays.asList("FOO", "BAR");
Iterable<String> iterable = () -> list.stream().map(String::toLowerCase).iterator();

for (String str : iterable) {
    System.out.println(str);
}
for (String str : iterable) {
    System.out.println(str);
}

出力:

foo
バー
foo
バー

これは、 Iterableが単一の抽象メソッドIterator<T> iterator()宣言するためにIterableIterator<T> iterator() 。これにより、効果的に、各呼び出しで新しいストリームを作成するラムダによって実装された機能的なインターフェイスになります。


一般的に、 Streamは次の図のように動作します。

ストリーム操作

:引数のチェックは、 ターミナル操作を行わなくても常に実行されます

try {
    IntStream.range(1, 10).filter(null);
} catch (NullPointerException e) {
    System.out.println("We got a NullPointerException as null was passed as an argument to filter()");
}

イデオンに生きる

出力:

filter()への引数としてnullが渡されたため、NullPointerExceptionが発生しました。

周波数マップの作成

groupingBy(classifier, downstream)コレクタは、グループ内の各要素を分類し、同じグループに分類された要素に対して下流操作を実行することによって、 Stream要素の集合をMapにすることを可能にする。

この原則の古典的な例は、 Mapを使用してStream内の要素の出現をカウントすることです。この例では、クラシファイアは単純にアイデンティティ関数であり、要素をそのまま返します。下流操作は、 counting()を使用して等しい要素の数をcounting()ます。

Stream.of("apple", "orange", "banana", "apple")
      .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
      .entrySet()
      .forEach(System.out::println);

下流操作は、それ自体がコレクタ( Collectors.counting() )です。このCollectors.counting()は、String型の要素で動作し、 Long型の結果を生成します。 collectメソッド呼び出しの結果はMap<String, Long>です。

これにより、次の出力が生成されます。

バナナ= 1
オレンジ= 1
リンゴ= 2

パラレルストリーム

注:使用するStreamを決定する前に、 ParallelStreamとSequential Streamの動作を確認してください。

Stream操作を同時に実行する場合は、これらの方法のいずれかを使用できます。

List<String> data = Arrays.asList("One", "Two", "Three", "Four", "Five");
Stream<String> aParallelStream = data.stream().parallel();

または:

Stream<String> aParallelStream = data.parallelStream();

並列ストリームに対して定義された演算を実行するには、端末演算子を呼び出します。

aParallelStream.forEach(System.out::println);

並列Streamからの(可能な)出力:



1

すべての要素が並列に処理されるよう順序が変更される場合があります(どちらがより速くそれを行うことができます )。順序が重要でない場合は、 parallelStream使用してください。

パフォーマンスへの影響

ケースネットワークが関与しているでは、並列Stream Sは、アプリケーションの全体的な性能を低下させることができるので、すべての並列Streamの使用ネットワークのための共通のフォーク参加スレッドプール。

一方、並列Streamは、現在実行中のCPUの使用可能なコアの数に応じて、他の多くのケースでパフォーマンスを大幅に向上させる可能性があります。

オプションのストリームを値のストリームに変換する

OptionalStreamを値のStreamに変換し、既存のOptional値のみを出力するOptionalます。 (つまり、 null値なしで、 Optional.empty()を処理しない)。

 Optional<String> op1 = Optional.empty();
 Optional<String> op2 = Optional.of("Hello World");

 List<String> result = Stream.of(op1, op2)
                             .filter(Optional::isPresent)
                             .map(Optional::get)
                             .collect(Collectors.toList());

 System.out.println(result); //[Hello World]

ストリームの作成

すべてのJava Collection<E>は、 Stream<E>を構築するためのstream()およびparallelStream()メソッドがあります。

Collection<String> stringList = new ArrayList<>();
Stream<String> stringStream = stringList.parallelStream();

Stream<E>は、次の2つの方法のいずれかを使用して配列から作成できます。

String[] values = { "aaa", "bbbb", "ddd", "cccc" };
Stream<String> stringStream = Arrays.stream(values);
Stream<String> stringStreamAlternative = Stream.of(values);

Arrays.stream()Stream.of()の違いは、 Stream.of()にはvarargsパラメータがあるため、次のように使用できます。

Stream<Integer> integerStream = Stream.of(1, 2, 3);

また、使用できるプリミティブなStreamもあります。例えば:

IntStream intStream = IntStream.of(1, 2, 3);
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0);

これらのプリミティブストリームは、 Arrays.stream()メソッドを使用して構築することもできます。

IntStream intStream = Arrays.stream(new int[]{ 1, 2, 3 });

指定された範囲の配列からStreamを作成することは可能です。

int[] values= new int[]{1, 2, 3, 4, 5};
IntStream intStram = Arrays.stream(values, 1, 3);

任意のプリミティブストリームは、 boxedメソッドを使用して、 boxedされたタイプのストリームに変換できます。

Stream<Integer> integerStream = intStream.boxed();

これは、プリミティブストリームにCollectorを引数として持つcollectメソッドがないため、データを収集する場合に便利な場合もあります。

ストリームチェーンの中間処理の再利用

これまでのターミナル操作が呼び出されると、ストリームは閉じられます。ターミナル操作だけが変化している場合、中間操作のストリームを再利用します。すべての中間操作が既に設定された状態で新しいストリームを構築するストリームサプライヤを作成することができます。

Supplier<Stream<String>> streamSupplier = () -> Stream.of("apple", "banana","orange", "grapes", "melon","blueberry","blackberry")
.map(String::toUpperCase).sorted();

  streamSupplier.get().filter(s ->   s.startsWith("A")).forEach(System.out::println);

// APPLE

  streamSupplier.get().filter(s -> s.startsWith("B")).forEach(System.out::println);

  // BANANA
  // BLACKBERRY
  // BLUEBERRY

int[]配列はストリームを使用してList<Integer>変換できます

int[] ints = {1,2,3};
List<Integer> list = IntStream.of(ints).boxed().collect(Collectors.toList());

数値ストリームに関する統計の検索

Javaの8と呼ばれるクラスを提供IntSummaryStatisticsDoubleSummaryStatisticsLongSummaryStatisticsなどの統計情報収集のための状態オブジェクトを与えcountminmaxsum 、およびaverage

Java SE 8
List<Integer> naturalNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = naturalNumbers.stream()
                                           .mapToInt((x) -> x)
                                           .summaryStatistics();
System.out.println(stats);

結果は:

Java SE 8
IntSummaryStatistics{count=10, sum=55, min=1, max=10, average=5.500000}

ストリームのスライスを取得する

例:コレクションの21番目から50番目までの要素を含む30個の要素のStreamを取得します。

final long n = 20L; // the number of elements to skip
final long maxSize = 30L; // the number of elements the stream should be limited to
final Stream<T> slice = collection.stream().skip(n).limit(maxSize);

ノート:

  • nが負またはmaxSizeが負の場合、 IllegalArgumentExceptionがスローされる
  • skip(long)limit(long)両方が中間操作です
  • ストリームにn未満の要素が含まれている場合、 skip(n)は空のストリームを返します
  • skip(long)limit(long)両方は、シーケンシャルストリームパイプラインでは安価な操作ですが、並行並列パイプラインでは非常に高価になります

ストリームを連結する

例の変数宣言:

Collection<String> abc = Arrays.asList("a", "b", "c");
Collection<String> digits = Arrays.asList("1", "2", "3");
Collection<String> greekAbc = Arrays.asList("alpha", "beta", "gamma");

例1 - 2つのStream連結する

final Stream<String> concat1 = Stream.concat(abc.stream(), digits.stream());

concat1.forEach(System.out::print);
// prints: abc123

例2 - 3つ以上のStream連結する

final Stream<String> concat2 = Stream.concat(
    Stream.concat(abc.stream(), digits.stream()),
    greekAbc.stream());

System.out.println(concat2.collect(Collectors.joining(", ")));
// prints: a, b, c, 1, 2, 3, alpha, beta, gamma

あるいはネストされた簡単にするためにconcat()構文Stream Sもと連結することができるflatMap()

final Stream<String> concat3 = Stream.of(
    abc.stream(), digits.stream(), greekAbc.stream())
    .flatMap(s -> s);
    // or `.flatMap(Function.identity());` (java.util.function.Function)

System.out.println(concat3.collect(Collectors.joining(", ")));
// prints: a, b, c, 1, 2, 3, alpha, beta, gamma

深く連結されたStream要素にアクセスすると、深いコールチェーンやStackOverflowExceptionが発生する可能性があるため、繰り返し連結からStream構築するときは注意してください。

IntStreamからStringへ

JavaにはCharストリームがありません。したがって、 StringStream of Characterを構築する場合、 String.codePoints()メソッドを使用してコードポイントのIntStreamを取得することができます。 IntStreamは以下のように取得できます:

public IntStream stringToIntStream(String in) {
  return in.codePoints();
}

つまり、変換を他の方法、つまりIntStreamToStringで行うのはもう少し複雑です。それは次のようにして行うことができます:

public String intStreamToString(IntStream intStream) {
  return intStream.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();
}

ストリームを使用して並べ替え

List<String> data = new ArrayList<>();
data.add("Sydney");
data.add("London");
data.add("New York");
data.add("Amsterdam");
data.add("Mumbai");
data.add("California");

System.out.println(data);

List<String> sortedData = data.stream().sorted().collect(Collectors.toList());

System.out.println(sortedData);

出力:

[Sydney, London, New York, Amsterdam, Mumbai, California]
[Amsterdam, California, London, Mumbai, New York, Sydney]

比較器を引数とするオーバーロードsortedバージョンがあるため、異なる比較メカニズムを使用することもできます。

また、ソートにラムダ式を使用することもできます。

List<String> sortedData2 = data.stream().sorted((s1,s2) -> s2.compareTo(s1)).collect(Collectors.toList());

これは[Sydney, New York, Mumbai, London, California, Amsterdam]

Comparator.reverseOrder()を使用すると、自然順序付けのreverseを行うコンパレータを使用できます。

List<String> reverseSortedData = data.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());

プリミティブのストリーム

JavaはIntStreamint )、 LongStreamlong )、 DoubleStreamdouble )の3種類のプリミティブに特化したStream提供します。それぞれのプリミティブの最適化された実装の他に、数学的操作のためのいくつかの特定の端末メソッドも提供します。例えば:

IntStream is = IntStream.of(10, 20, 30);
double average = is.average().getAsDouble(); // average is 20.0

ストリームの結果を配列に集める

Analogは、 collect()によってStreamコレクションを取得するために、 Stream.toArray()メソッドで配列を取得できます。

List<String> fruits = Arrays.asList("apple", "banana", "pear", "kiwi", "orange");

String[] filteredFruits = fruits.stream()
    .filter(s -> s.contains("a"))
    .toArray(String[]::new);     

// prints: [apple, banana, pear, orange]
System.out.println(Arrays.toString(filteredFruits));

String[]::newは、特別な種類のメソッド参照です。コンストラクタ参照です。

述語と一致する最初の要素の検索

条件に一致するStream最初の要素を見つけることができます。

この例では、正方形が50000超える最初のIntegerを見つけるでしょう。

IntStream.iterate(1, i -> i + 1) // Generate an infinite stream 1,2,3,4...
    .filter(i -> (i*i) > 50000) // Filter to find elements where the square is >50000
    .findFirst(); // Find the first filtered element

この式は結果と共にOptionalIntを返します。

無限のStream 、Javaは結果を見つけるまで各要素のチェックを続けます。有限Stream場合、Javaが要素を使い果たしたにもかかわらず結果を見つけることができない場合は、空のOptionalInt返します。

IntStreamを使用してインデックスを反復処理する

要素のStreamは通常、現在の項目のインデックス値にアクセスできません。インデックスにアクセスしながら配列またはArrayListを反復処理するには、 IntStream.range(start, endExclusive)使用します。

String[] names = { "Jon", "Darin", "Bauke", "Hans", "Marc" };

IntStream.range(0, names.length)
    .mapToObj(i -> String.format("#%d %s", i + 1, names[i]))
    .forEach(System.out::println);

range(start, endExclusive)メソッドは別のÌntStreamを返し、 mapToObj(mapper)Stringストリームを返します。

出力:

#1ジョン
#2ダーリン
#3 Bauke
#4ハンズ
#5マーク

これは、通常のforループをカウンタで使用するのと非常によく似ていますが、パイプライン化と並列化の利点があります。

for (int i = 0; i < names.length; i++) {
    String newName = String.format("#%d %s", i + 1, names[i]);
    System.out.println(newName);
}

flatMap()でストリームを平坦化する

順番にストリーミング可能なアイテムのStreamは、単一の連続Streamフラット化することができます。

項目リストの配列は単一のリストに変換できます。

List<String> list1 =  Arrays.asList("one", "two");
      List<String> list2 =  Arrays.asList("three","four","five");
      List<String> list3 =  Arrays.asList("six");
          List<String> finalList = Stream.of(list1, list2, list3).flatMap(Collection::stream).collect(Collectors.toList());
System.out.println(finalList);

// [one, two, three, four, five, six]

項目リストを値として含むマップを結合リストに展開することができます

Map<String, List<Integer>> map = new LinkedHashMap<>();
map.put("a", Arrays.asList(1, 2, 3));
map.put("b", Arrays.asList(4, 5, 6));

List<Integer> allValues = map.values() // Collection<List<Integer>>
        .stream()                      // Stream<List<Integer>>
        .flatMap(List::stream)         // Stream<Integer>
        .collect(Collectors.toList());

System.out.println(allValues);
// [1, 2, 3, 4, 5, 6]

Map Listを単一の連続Streamまとめることができます

List<Map<String, String>> list = new ArrayList<>();
Map<String,String> map1 = new HashMap();
map1.put("1", "one");
map1.put("2", "two");

Map<String,String> map2 = new HashMap();
map2.put("3", "three");
map2.put("4", "four");
list.add(map1);
list.add(map2);


Set<String> output= list.stream()  //  Stream<Map<String, String>>
    .map(Map::values)              // Stream<List<String>>
    .flatMap(Collection::stream)   // Stream<String>
    .collect(Collectors.toSet());  //Set<String>
// [one, two, three,four]

ストリームに基づいた地図を作成する

キーが重複しない単純なケース

Stream<String> characters = Stream.of("A", "B", "C");

Map<Integer, String> map = characters
            .collect(Collectors.toMap(element -> element.hashCode(), element -> element));
// map = {65=A, 66=B, 67=C}

より宣言的なものにするために、 Functionインタフェース - Function.identity()静的メソッドを使用することができます。このlambda element -> elementFunction.identity()置き換えることができます。

キーが重複している場合

Collectors.toMapjavadocの状態は次のとおりです。

マッピングされたキーに重複が含まれている場合( Object.equals(Object)に従って)、コレクション操作が実行されるとIllegalStateExceptionがスローされます。マッピングされたキーに重複がある場合は、代わりにtoMap(Function, Function, BinaryOperator)使用します。

Stream<String> characters = Stream.of("A", "B", "B", "C");

Map<Integer, String> map = characters
            .collect(Collectors.toMap(
                element -> element.hashCode(),
                element -> element,
                (existingVal, newVal) -> (existingVal + newVal)));

// map = {65=A, 66=BB, 67=C}

Collectors.toMap(...)渡されたBinaryOperatorは、衝突の場合に格納される値を生成します。できる:

  • ストリームの最初の値が優先されるように古い値を返し、
  • ストリームの最後の値が優先されるように新しい値を返します。
  • 古い値と新しい値を結合する

価値によるグループ分け

カスケード接続されたデータベースの「グループ化」操作と同等の処理を実行する必要がある場合は、 Collectors.groupingByを使用できます。説明すると、次の例では、人々の名前が姓にマップされたマップが作成されます。

List<Person> people = Arrays.asList(
    new Person("Sam", "Rossi"),
    new Person("Sam", "Verdi"),
    new Person("John", "Bianchi"),
    new Person("John", "Rossi"),
    new Person("John", "Verdi")
);

Map<String, List<String>> map = people.stream()
        .collect(
                // function mapping input elements to keys
                Collectors.groupingBy(Person::getName, 
                // function mapping input elements to values,
                // how to store values
                Collectors.mapping(Person::getSurname, Collectors.toList()))
        );

// map = {John=[Bianchi, Rossi, Verdi], Sam=[Rossi, Verdi]}

イデオンに生きる

ストリームを使用したランダムな文字列の生成

WebサービスのセッションIDや、アプリケーションの登録後の初期パスワードなど、ランダムなStringsを作成すると便利なことがあります。これはStreamを使って簡単に実現できます。

まず、乱数ジェネレータを初期化する必要があります。生成されたStringのセキュリティを強化するには、 SecureRandomを使用することをお勧めします。

SecureRandom作成は非常にコストがかかるので、一度だけこれを実行し、 setSeed()メソッドの1つを呼び出して再シードすることをおsetSeed()します。

private static final SecureRandom rng = new SecureRandom(SecureRandom.generateSeed(20)); 
//20 Bytes as a seed is rather arbitrary, it is the number used in the JavaDoc example

ランダムなStringを作成するとき、通常は文字(例えば文字と数字のみ)のみを使用します。したがって、後でStreamをフィルタリングするために使用できるbooleanを返すメソッドを作成できます。

//returns true for all chars in 0-9, a-z and A-Z
boolean useThisCharacter(char c){
    //check for range to avoid using all unicode Letter (e.g. some chinese symbols)
    return c >= '0' && c <= 'z' && Character.isLetterOrDigit(c);
}

次に、RNGを使用して、 useThisCharacterチェックを渡す文字セットを含む特定の長さのランダムなStringを生成することができます。

public String generateRandomString(long length){
    //Since there is no native CharStream, we use an IntStream instead
    //and convert it to a Stream<Character> using mapToObj.
    //We need to specify the boundaries for the int values to ensure they can safely be cast to char
    Stream<Character> randomCharStream = rng.ints(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT).mapToObj(i -> (char)i).filter(c -> this::useThisCharacter).limit(length);

    //now we can use this Stream to build a String utilizing the collect method.
    String randomString = randomCharStream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();
    return randomString;
}

ストリームを使用した数学関数の実装

Stream s、特にIntStreamは、加算項(Σ)を実装するエレガントな方法です。 Streamの範囲は、合計の範囲として使用できます。

例えば、MadhavaのPiの近似式は次の式で与えられます(出典: wikipedia )。 Madhavaの近似

これは任意の精度で計算できます。例えば、101項の場合:

double pi = Math.sqrt(12) * 
            IntStream.rangeClosed(0, 100)
                     .mapToDouble(k -> Math.pow(-3, -1 * k) / (2 * k + 1))
                     .sum();

注意: doubleの精度では、29の上限を選択するだけで、 Math.Piと区別できない結果を得るには十分Math.Pi

ストリームおよびメソッド参照を使用した自己文書化プロセスの記述

メソッド参照は優れた自己文書化コードを作成し、 Streamメソッド参照を使用すると、複雑なプロセスを読みやすく理解することができます。次のコードを考えてみましょう:

public interface Ordered {
    default int getOrder(){
        return 0;
    }
}

public interface Valued<V extends Ordered> {
    boolean hasPropertyTwo();
    V getValue();
}

public interface Thing<V extends Ordered> {
    boolean hasPropertyOne();
    Valued<V> getValuedProperty();
}

public <V extends Ordered> List<V> myMethod(List<Thing<V>> things) {
    List<V> results = new ArrayList<V>();
    for (Thing<V> thing : things) {
        if (thing.hasPropertyOne()) {
            Valued<V> valued = thing.getValuedProperty();
            if (valued != null && valued.hasPropertyTwo()){
                V value = valued.getValue();
                if (value != null){
                    results.add(value);
                }
            }
        }
    }
    results.sort((a, b)->{
        return Integer.compare(a.getOrder(), b.getOrder());
    });
    return results;
}

使用して書き換え、この最後の方法Stream sおよび方法参照がはるかに読みやすいですし、プロセスの各ステップを迅速かつ容易に理解されている-それだけ短くはない、それはまた、インタフェースとクラスは、各ステップでコードを担当している一目で示しています。

public <V extends Ordered> List<V> myMethod(List<Thing<V>> things) {
    return things.stream()
        .filter(Thing::hasPropertyOne)
        .map(Thing::getValuedProperty)
        .filter(Objects::nonNull)
        .filter(Valued::hasPropertyTwo)
        .map(Valued::getValue)
        .filter(Objects::nonNull)
        .sorted(Comparator.comparing(Ordered::getOrder))
        .collect(Collectors.toList());
}    

Map.Entryのストリームを使用してマッピング後の初期値を保持する

Streamを作成する必要がある場合はマップする必要がありますが、初期値も保持したい場合は、次のようなユーティリティメソッドを使用してStreamMap.Entry<K,V>マップできます。

public static <K, V> Function<K, Map.Entry<K, V>> entryMapper(Function<K, V> mapper){
    return (k)->new AbstractMap.SimpleEntry<>(k, mapper.apply(k));
}

次に、コンバータを使用して、元の値とマップされた値の両方にアクセスできるStreamを処理できます。

Set<K> mySet;
Function<K, V> transformer = SomeClass::transformerMethod;
Stream<Map.Entry<K, V>> entryStream = mySet.stream()
    .map(entryMapper(transformer));

その後、通常どおりStreamを処理し続けることができます。これにより、中間コレクションを作成するオーバーヘッドが回避されます。

ストリーム操作カテゴリ

ストリーム操作は、中間操作と端末操作の2つの主なカテゴリと、ステートレスとステートフルの2つのサブカテゴリに分類されます。


中間処理:

中間的な操作は、単純なStream.mapように常に怠惰 Stream.map 。ストリームが実際に消費されるまで呼び出されません。これは簡単に検証できます:

Arrays.asList(1, 2 ,3).stream().map(i -> {
    throw new RuntimeException("not gonna happen");
    return i;
});

中間操作は、ストリームの共通のビルディングブロックであり、ソースの後に連鎖され、通常、ストリームチェーンをトリガーするターミナル操作が続きます。


ターミナル操作

ターミナル操作は、ストリームの消費をトリガーするものです。より一般的なのは、 Stream.forEachまたはStream.collectです。彼らは通常、中間作業の連鎖の後に配置され、ほとんど常に熱望しています。


ステートレス操作

ステートレス性とは、各アイテムが他のアイテムのコンテキストなしで処理されることを意味します。ステートレス操作では、ストリームをメモリ効率的に処理できます。ストリームの他の項目に関する情報を必要としないStream.mapStream.filterような操作は、ステートレスとみなされます。


ステートフルな操作

ステートフル性とは、各項目の操作がストリームの他の項目に依存することを意味します。これには、状態を保持する必要があります。ステートフル・オペレーションは、長い、または無限のストリームで中断することがあります。 Stream.sortedような操作では、項目が放出される前にストリームの全体が処理され、十分長いストリームの項目で中断されます。これは長いストリームで示されます( あなたの責任において実行してください ):

// works - stateless stream
long BIG_ENOUGH_NUMBER = 999999999;
IntStream.iterate(0, i -> i + 1).limit(BIG_ENOUGH_NUMBER).forEach(System.out::println);

これにより、 Stream.sortedステートフルな状態のためにメモリ不足が発生します。

// Out of memory - stateful stream
IntStream.iterate(0, i -> i + 1).limit(BIG_ENOUGH_NUMBER).sorted().forEach(System.out::println);

イテレータをストリームに変換する

イテレータをストリームに変換するには、 Spliterators.spliterator()またはSpliterators.spliteratorUnknownSize()を使用します。

Iterator<String> iterator = Arrays.asList("A", "B", "C").iterator();    
Spliterator<String> spliterator = Spliterators.spliteratorUnknownSize(iterator, 0);
Stream<String> stream = StreamSupport.stream(spliterator, false);

ストリームによる削減

削減とは、ストリームのすべての要素に2項演算子を適用して1つの値にするプロセスです。

IntStreamsum()メソッドは削減の例です。ストリームのすべての項に加算を適用し、最終的な値が1になります。 合計減算

これは、 (((1+2)+3)+4)

ストリームのreduceメソッドを使用すると、カスタムリダクションを作成できます。 reduceメソッドを使用してsum()メソッドを実装することは可能です。

IntStream istr;
    
//Initialize istr
    
OptionalInt istr.reduce((a,b)->a+b);

Optionalバージョンが返され、空のストリームを適切に処理できるようになります。

縮小の別の例は、 Stream<LinkedList<T>>を単一のLinkedList<T>結合することです。

Stream<LinkedList<T>> listStream;
    
//Create a Stream<LinkedList<T>>
    
Optional<LinkedList<T>> bigList = listStream.reduce((LinkedList<T> list1, LinkedList<T> list2)->{
    LinkedList<T> retList = new LinkedList<T>();
    retList.addAll(list1);
    retList.addAll(list2);
    return retList;
});

アイデンティティ要素を提供することもできます 。たとえば、 x+0==xように、追加の恒等要素は0です。乗算の場合、恒等要素は1であり、 x*1==x 。上記の場合、空のリストを別のリストに追加すると、「追加する」リストは変更されないため、identity要素は空のLinkedList<T>です。

Stream<LinkedList<T>> listStream;

//Create a Stream<LinkedList<T>>

LinkedList<T> bigList = listStream.reduce(new LinkedList<T>(), (LinkedList<T> list1, LinkedList<T> list2)->{
    LinkedList<T> retList = new LinkedList<T>();
    retList.addAll(list1);
    retList.addAll(list2);
    return retList;
});

identity要素が指定されている場合、戻り値は空の​​ストリームで呼び出されたOptional -ifでラップされないことに注意してください。reduce reduce()はidentity要素を返します。

バイナリ演算子は、 (a+b)+c==a+(b+c)意味する連想型でなければなりません。これは、要素を任意の順序で減らすことができるためです。例えば、上記のような追加の削減は、次のように実行できます。

その他の合計減額

この削減は、 ((1+2)+(3+4))書き込みと同等です。また、 Streamの一部を各プロセッサで減らすことができ、最後に各プロセッサの結果を組み合わせることで削減することができます。

単一のStringにストリームを結合する

頻繁に使用されるユースケースは、ストリーム項目を特定の文字で区切ったストリームからStringを作成することです。次の例のように、 Collectors.joining()メソッドをこのために使用できます。

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");

String result = fruitStream.filter(s -> s.contains("a"))
           .map(String::toUpperCase)
           .sorted()
           .collect(Collectors.joining(", "));
           
System.out.println(result);

出力:

アップル、バナナ、オレンジ、PEAR

Collectors.joining()メソッドは、プリフィックスとポストフィックスを処理することもできます。

String result = fruitStream.filter(s -> s.contains("e"))
           .map(String::toUpperCase)
           .sorted()
           .collect(Collectors.joining(", ", "Fruits: ", "."));
           
System.out.println(result);

出力:

フルーツ:アップル、オレンジ、PEAR。

イデオンに生きる



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow