Java 8 Stream – Java Stream

ようこそ、Java 8 Stream API チュートリアルへ。最後のいくつかの Java 8 ポストでは、Java 8 インターフェースの変更関数型インターフェースとラムダ式を見てきました。今日は、Java 8 で導入された主要な API の1つ、Java Streamに焦点を当てます。

Java 8 Stream

  1. Java 8 Stream
  2. Collections and Java Stream
  3. Java 8 Stream の関数型インターフェース
    1. Function と BiFunction
    2. Predicate と BiPredicate
    3. Consumer と BiConsumer
    4. Supplier
  4. java.util.Optional
  5. java.util.Spliterator
  6. Javaストリームの中間および終端操作
  7. Javaストリームのショートサーキット操作
  8. Javaストリームの例
    1. Javaストリームの作成
    2. Javaストリームをコレクションまたは配列に変換
    3. Javaストリームの中間操作
    4. Javaストリームの終端操作
  9. Java 8ストリームAPIの制限

Javaストリーム

JavaストリームAPIの例を見る前に、なぜ必要だったかを見てみましょう。整数のリストを反復処理し、10より大きいすべての整数の合計を求めたいとします。Java 8以前は、それを行うためのアプローチが必要でした。

private static int sumIterator(List<Integer> list) {
	Iterator<Integer> it = list.iterator();
	int sum = 0;
	while (it.hasNext()) {
		int num = it.next();
		if (num > 10) {
			sum += num;
		}
	}
	return sum;
}

上記のアプローチには3つの主な問題があります:

  1. われわれは整数の合計だけを知りたいのですが、反復がどのように行われるかも提供しなければなりません。これはまた外部反復と呼ばれ、クライアントプログラムがリストを反復処理するアルゴリズムを処理しているためです。
  2. プログラムは連続的な性質を持っており、簡単に並列で行うことはできません。
  3. 単純なタスクでも多くのコードが必要です。

上記のすべての欠点を克服するために、Java 8 Stream APIが導入されました。Java Stream APIを使用して内部反復を実装できます。これは、Javaフレームワークが反復を制御しているため、より優れています。内部反復には、シーケンシャルおよび並列実行、指定された基準に基づくフィルタリング、マッピングなど、いくつかの機能が備わっています。Java 8 Stream APIのほとんどのメソッド引数は関数インターフェースであり、それによりラムダ式が非常にうまく機能します。Java Streamsを使用して上記のロジックを1行のステートメントでどのように書くかを見てみましょう。

private static int sumStream(List<Integer> list) {
	return list.stream().filter(i -> i > 10).mapToInt(i -> i).sum();
}

上記のプログラムはJavaフレームワークの反復戦略、フィルタリングおよびマッピングメソッドを利用しており、効率が向上します。まず、Java 8 Stream APIの核心概念を見てから、最も一般的に使用されるメソッドを理解するためのいくつかの例を見ていきます。

コレクションとJava Stream

A collection is an in-memory data structure to hold values and before we start using collection, all the values should have been populated. Whereas a java Stream is a data structure that is computed on-demand. Java Stream doesn’t store data, it operates on the source data structure (collection and array) and produce pipelined data that we can use and perform specific operations. Such as we can create a stream from the list and filter it based on a condition. Java Stream operations use functional interfaces, that makes it a very good fit for functional programming using lambda expression. As you can see in the above example that using lambda expressions make our code readable and short. Java 8 Stream internal iteration principle helps in achieving lazy-seeking in some of the stream operations. For example filtering, mapping, or duplicate removal can be implemented lazily, allowing higher performance and scope for optimization. Java Streams are consumable, so there is no way to create a reference to stream for future usage. Since the data is on-demand, it’s not possible to reuse the same stream multiple times. Java 8 Stream support sequential as well as parallel processing, parallel processing can be very helpful in achieving high performance for large collections. All the Java Stream API interfaces and classes are in the java.util.stream package. Since we can use primitive data types such as int, long in the collections using auto-boxing and these operations could take a lot of time, there are specific classes for primitive types – IntStream, LongStream and DoubleStream.

Java 8 Streamの機能インターフェース

Java 8 Stream APIメソッドでよく使用される機能インターフェースのいくつかは次のとおりです:

  1. FunctionとBiFunction: Functionは、1種類の引数を取り、別の種類の引数を返す関数を表します。Function<T, R>は、Tが関数への入力の型であり、Rが関数の結果の型であるジェネリック形式です。プリミティブ型を扱うために、特定のFunctionインターフェースがあります – ToIntFunctionToLongFunctionToDoubleFunctionToIntBiFunctionToLongBiFunctionToDoubleBiFunctionLongToIntFunctionLongToDoubleFunctionIntToLongFunctionIntToDoubleFunctionなど。Functionまたはそのプリミティブの特殊化が使用されるStreamメソッドのいくつかは次のとおりです:
    • <R> Stream<R> map(Function<? super T, ? extends R> mapper)
    • IntStream mapToInt(ToIntFunction<? super T> mapper) – 同様にlongおよびdoubleを返すプリミティブ固有のストリームもあります。
    • IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper) – 同様にlongおよびdouble
    • <A> A[] toArray(IntFunction<A[]> generator)
    • <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
  2. PredicateおよびBiPredicate:これはストリームの要素に対してテストされる述語を表します。これはJavaストリームから要素をフィルタリングするために使用されます。Functionと同様に、int、long、およびdouble用のプリミティブ固有のインターフェースもあります。PredicateまたはBiPredicateの特殊化が使用されるStreamメソッドのいくつかは次のとおりです:
    • Stream<T> filter(Predicate<? super T> predicate)
    • boolean anyMatch(Predicate<? super T> predicate)
    • boolean allMatch(Predicate<? super T> predicate)
    • boolean noneMatch(Predicate<? super T> predicate)
  3. コンシューマーとバイコンシューマー: それは単一の入力引数を受け入れて結果を返す操作を表します。これはJavaストリームのすべての要素に対してアクションを実行するために使用できます。いくつかのJava 8 Streamメソッドで使用されるConsumerBiConsumerまたはそのプリミティブな特殊化インターフェースには以下があります:
    • Stream<T> peek(Consumer<? super T> action)
    • void forEach(Consumer<? super T> action)
    • void forEachOrdered(Consumer<? super T> action)
  4. サプライヤ: サプライヤはストリーム内で新しい値を生成するための操作を表します。Supplier引数を取るStreamのいくつかのメソッドには以下があります:
    • public static<T> Stream<T> generate(Supplier<T> s)
    • <R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner)

java.util.Optional

Java Optionalは、ヌルでない値を含むかもしれないコンテナオブジェクトです。値が存在する場合、isPresent()はtrueを返し、get()はその値を返します。ストリームの終端操作はOptionalオブジェクトを返します。これらのメソッドの一部は次のとおりです:

  • Optional<T> reduce(BinaryOperator<T> accumulator)
  • Optional<T> min(Comparator<? super T> comparator)
  • Optional<T> max(Comparator<? super T> comparator)
  • Optional<T> findFirst()
  • Optional<T> findAny()

java.util.Spliterator

Java 8 Stream APIで並行実行をサポートするために、Spliteratorインターフェースが使用されます。SpliteratorのtrySplitメソッドは、元のSpliteratorの要素のサブセットを管理する新しいSpliteratorを返します。

Java Streamの中間および終端操作

Java Stream API の新しい Stream を返す操作は、中間操作と呼ばれます。これらの操作はほとんどの場合、遅延性を持っており、新しいストリーム要素を生成して次の操作に送信します。中間操作は、最終的な結果を生成する操作ではありません。一般的に使用される中間操作には、filtermap があります。Java 8 の Stream API の操作は、結果を返すか副作用を生成します。ストリーム上でターミナルメソッドが呼び出されると、ストリームが消費され、その後はストリームを使用することはできません。ターミナル操作は、要素すべてを処理してから結果を返すため、積極的な性質を持ちます。一般的に使用されるターミナルメソッドには、forEachtoArrayminmaxfindFirstanyMatchallMatch などがあります。戻り値の型からターミナルメソッドを特定することができます。ターミナルメソッドは常に Stream を返しません。

Java Stream ショートサーキット操作

中間操作は、無限ストリームに対して有限のストリームを生成する場合に、ショートサーキットと呼ばれます。たとえば、limit()skip() は、2 つのショートサーキット中間操作です。ターミナル操作は、無限ストリームに対して有限の時間で終了する場合に、ショートサーキットと呼ばれます。たとえば、anyMatchallMatchnoneMatchfindFirstfindAny は、ショートサーキットのターミナル操作です。

Java Streamの例

I have covered almost all the important parts of the Java 8 Stream API. It’s exciting to use this new API features and let’s see it in action with some java stream examples.

Javaストリームの作成

配列やコレクションからJavaストリームを作成する方法はいくつかあります。これらを簡単な例で見てみましょう。

  1. Stream.of()を使用して、同じタイプのデータからストリームを作成できます。たとえば、intまたはIntegerオブジェクトのグループから整数のJavaストリームを作成できます。

    Stream<Integer> stream = Stream.of(1,2,3,4);
    
  2. Stream.of()を使用して、オブジェクトの配列からストリームを返すことができます。ただし、オートボクシングはサポートされていないため、プリミティブ型の配列を渡すことはできません。

    Stream<Integer> stream = Stream.of(new Integer[]{1,2,3,4}); 
    //動作します
    
    Stream<Integer> stream1 = Stream.of(new int[]{1,2,3,4}); 
    //コンパイル時エラー、型の不一致:Stream<int[]>をStream<Integer>に変換できません
    
  3. Collection stream()を使用して順次ストリームを作成し、parallelStream()を使用して並行ストリームを作成できます。

    List<Integer> myList = new ArrayList<>();
    for(int i=0; i<100; i++) myList.add(i);
    		
    //順次ストリーム
    Stream<Integer> sequentialStream = myList.stream();
    		
    //並行ストリーム
    Stream<Integer> parallelStream = myList.parallelStream();
    
  4. Stream.generate()メソッドとStream.iterate()メソッドを使用してストリームを作成できます。

    Stream<String> stream1 = Stream.generate(() -> {return "abc";});
    Stream<String> stream2 = Stream.iterate("abc", (i) -> i);
    
  5. Arrays.stream()およびString.chars()メソッドを使用します。

    LongStream is = Arrays.stream(new long[]{1,2,3,4});
    IntStream is2 = "abc".chars();
    

Javaストリームをコレクションまたは配列に変換

Javaのストリームからコレクションや配列を取得する方法はいくつかあります。

  1. java Streamのcollect()メソッドを使用して、ストリームからList、Map、またはSetを取得できます。

    Stream<Integer> intStream = Stream.of(1,2,3,4);
    List<Integer> intList = intStream.collect(Collectors.toList());
    System.out.println(intList); //prints [1, 2, 3, 4]
    
    intStream = Stream.of(1,2,3,4); //stream is closed, so we need to create it again
    Map<Integer,Integer> intMap = intStream.collect(Collectors.toMap(i -> i, i -> i+10));
    System.out.println(intMap); //prints {1=11, 2=12, 3=13, 4=14}
    
  2. streamのtoArray()メソッドを使用して、ストリームから配列を作成できます。

    Stream<Integer> intStream = Stream.of(1,2,3,4);
    Integer[] intArray = intStream.toArray(Integer[]::new);
    System.out.println(Arrays.toString(intArray)); //prints [1, 2, 3, 4]
    

Java Stream中間操作

一般的に使用されるjava Stream中間操作の例を見てみましょう。

  1. Stream filter() の例: filter() メソッドを使用してストリーム要素を条件にテストし、フィルター処理されたリストを生成できます。

    List<Integer> myList = new ArrayList<>();
    for(int i=0; i<100; i++) myList.add(i);
    Stream<Integer> sequentialStream = myList.stream();
    
    Stream<Integer> highNums = sequentialStream.filter(p -> p > 90); // 90 より大きい数字をフィルタリング
    System.out.print("90 より大きい数字=");
    highNums.forEach(p -> System.out.print(p+" "));
    //prints "90 より大きい数字=91 92 93 94 95 96 97 98 99 "
    
  2. Stream map() の例: map() を使用して関数をストリームに適用できます。文字列のリストに大文字関数を適用する方法を見てみましょう。

    Stream<String> names = Stream.of("aBc", "d", "ef");
    System.out.println(names.map(s -> {
    		return s.toUpperCase();
    	}).collect(Collectors.toList()));
    //prints [ABC, D, EF]
    
  3. Stream sorted() の例: Comparator 引数を渡すことで、sorted() を使用してストリーム要素をソートできます。

    Stream<String> names2 = Stream.of("aBc", "d", "ef", "123456");
    List<String> reverseSorted = names2.sorted(Comparator.reverseOrder()).collect(Collectors.toList());
    System.out.println(reverseSorted); // [ef, d, aBc, 123456]
    
    Stream<String> names3 = Stream.of("aBc", "d", "ef", "123456");
    List<String> naturalSorted = names3.sorted().collect(Collectors.toList());
    System.out.println(naturalSorted); //[123456, aBc, d, ef]
    
  4. Stream flatMap() の例: flatMap() を使用して、リストのストリームからストリームを作成できます。この疑問を解消するために、簡単な例を見てみましょう。

    Stream<List<String>> namesOriginalList = Stream.of(
    	Arrays.asList("Pankaj"), 
    	Arrays.asList("David", "Lisa"),
    	Arrays.asList("Amit"));
    //flat the stream from List<String> to String stream
    Stream<String> flatStream = namesOriginalList
    	.flatMap(strList -> strList.stream());
    
    flatStream.forEach(System.out::println);
    

Java Streamの端末操作

いくつかのJavaストリームの端末操作の例を見てみましょう。

  1. reduce()メソッドの例: reduce()メソッドを使用して、ストリームの要素に対して連想的な蓄積関数を使用して縮約を実行し、Optionalを返すことができます。ストリーム内の整数を乗算する方法を見てみましょう。

    Stream<Integer> numbers = Stream.of(1,2,3,4,5);
    		
    Optional<Integer> intOptional = numbers.reduce((i,j) -> {return i*j;});
    if(intOptional.isPresent()) System.out.println("Multiplication = "+intOptional.get()); //120
    
  2. count()メソッドの例: この端末操作を使用して、ストリーム内のアイテムの数を数えることができます。

    Stream<Integer> numbers1 = Stream.of(1,2,3,4,5);
    		
    System.out.println("Number of elements in stream="+numbers1.count()); //5
    
  3. Stream forEach()の例: これはストリームを反復処理するために使用できます。イテレーターの代わりにこれを使用できます。ストリームのすべての要素を印刷する方法を見てみましょう。

    Stream<Integer> numbers2 = Stream.of(1,2,3,4,5);
    numbers2.forEach(i -> System.out.print(i+",")); //1,2,3,4,5,
    
  4. Stream match()の例: Stream APIの一致メソッドのいくつかの例を見てみましょう。

    Stream<Integer> numbers3 = Stream.of(1,2,3,4,5);
    System.out.println("Streamに4が含まれていますか? "+numbers3.anyMatch(i -> i==4));
    //Streamに4が含まれていますか? true
    
    Stream<Integer> numbers4 = Stream.of(1,2,3,4,5);
    System.out.println("Streamにすべての要素が10未満ですか? "+numbers4.allMatch(i -> i<10));
    //Streamにすべての要素が10未満ですか? true
    
    Stream<Integer> numbers5 = Stream.of(1,2,3,4,5);
    System.out.println("Streamに10が含まれていませんか? "+numbers5.noneMatch(i -> i==10));
    //Streamに10が含まれていませんか? true
    
  5. findFirst()メソッドの例:これはショートサーキット終端操作です。ストリームから最初の文字列を見つける方法を見てみましょう。Dで始まる。

    Stream<String> names4 = Stream.of("Pankaj","Amit","David", "Lisa");
    Optional<String> firstNameWithD = names4.filter(i -> i.startsWith("D")).findFirst();
    if(firstNameWithD.isPresent()){
    	System.out.println("First Name starting with D="+firstNameWithD.get()); //David
    }
    

Java 8 Stream APIの制限

Java 8 Stream APIはリストと配列を操作するための多くの新機能を提供していますが、いくつかの制限もあります。

  1. 状態を持たないラムダ式: パラレルストリームを使用し、ラムダ式が状態を持つ場合、ランダムな応答が生じる可能性があります。簡単なプログラムで確認しましょう。 StatefulParallelStream.java

    package com.journaldev.java8.stream;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Stream;
    
    public class StatefulParallelStream {
    
    	public static void main(String[] args) {
    
    		List<Integer> ss = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
    		List<Integer> result = new ArrayList<Integer>();
    		 
    		Stream<Integer> stream = ss.parallelStream();
    		 
    		stream.map(s -> {
    		        synchronized (result) {
    		          if (result.size() < 10) {
    		            result.add(s);
    		          }
    		        }
    				return s;
    		    }).forEach( e -> {});
    		 System.out.println(result);   
    	}
    }
    

    上記のプログラムを実行すると、ストリームが反復される方法に依存するため、異なる結果が得られます。並列処理に対して明示的な順序が定義されていないためです。シーケンシャルストリームを使用すると、この問題は発生しません。

  2. ストリームが消費されると、後で使用できなくなります。上記の例でわかるように、私は毎回ストリームを作成しています。

  3. Stream APIには多くのメソッドがあり、最も混乱する部分はオーバーロードされたメソッドです。学習曲線が時間を要します。

これでJava 8 Streamの例チュートリアルは終わりです。この機能を使用して、並行処理を通じてコードを読みやすくし、パフォーマンスを向上させることを楽しみにしています。参考: Java Stream API Doc

Source:
https://www.digitalocean.com/community/tutorials/java-8-stream