Java Language
ストリーム
サーチ…
前書き
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);
出力:
林檎
バナナ
オレンジ
梨
上記のコードで実行される操作は、次のように要約できます。
Stream<String>
ファクトリメソッドStream.of(values)
を使用して、シーケンスされた順序付けられたフルーツString
要素のStream
を含むStream<String>
を作成します。filter()
オペレーションは、与えられた述語(述語によってテストされたときにtrueを返す要素)と一致する要素だけを保持します。この場合、"a"
を含む要素は保持されます。述語はラムダ式として与えられます 。map()
操作は、マッパーと呼ばれる特定の関数を使用して各要素を変換します。この場合、各フルーツString
は、 メソッド参照String::toUppercase
を使用して、大文字のString
バージョンにマップされます。ことに留意されたい
map()
マッピング関数は、入力パラメータに異なるタイプを返した場合、操作は異なる一般的なタイプのストリームを返します。たとえば、Stream<String>
で.map(String::isEmpty)
呼び出すと、Stream<Boolean>
sorted()
オペレーションは、自然順序付け(String
の場合は辞書編集的にsorted()
に従って、Stream
の要素をソートします。最後に、
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
は要素への直接アクセスと操作を提供しません。それは集合的エンティティとしてのオブジェクトのグループに専念し、そのエンティティ全体に対して操作を実行する。 Stream
とCollection
は、これらの異なる目的のための別個の高レベル抽象である。
コレクションの要素をコレクションに集める
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
は無限ループに入ります。 Stream
のlimit
メソッドは、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()
宣言するためにIterable
しIterator<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の使用可能なコアの数に応じて、他の多くのケースでパフォーマンスを大幅に向上させる可能性があります。
オプションのストリームを値のストリームに変換する
Optional
のStream
を値の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と呼ばれるクラスを提供IntSummaryStatistics
、 DoubleSummaryStatistics
とLongSummaryStatistics
などの統計情報収集のための状態オブジェクトを与えcount
、 min
、 max
、 sum
、およびaverage
。
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);
結果は:
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ストリームがありません。したがって、 String
てStream
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はIntStream
( int
)、 LongStream
( long
)、 DoubleStream
( double
)の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 -> element
をFunction.identity()
置き換えることができます。
キーが重複している場合
Collectors.toMap
のjavadocの状態は次のとおりです。
マッピングされたキーに重複が含まれている場合(
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 )。
これは任意の精度で計算できます。例えば、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
を作成する必要がある場合はマップする必要がありますが、初期値も保持したい場合は、次のようなユーティリティメソッドを使用してStream
をMap.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.map
やStream.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つの値にするプロセスです。
IntStream
のsum()
メソッドは削減の例です。ストリームのすべての項に加算を適用し、最終的な値が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。