Java 8 Streamsチュートリアル – コード例で学ぶStream APIの基本と使い方
この記事では、Java 8のStreams(ストリーム)機能について詳しく解説し、さまざまなコード例を交えながらその使い方を紹介します。
Java StreamsはJavaに関数型プログラミングをもたらす強力な機能です。Java 8から導入されたため、それ以前のバージョンを使用している場合は、Java 8以降へのアップグレードが必要です。
なぜJava Streamsを使うべきなのか?
Streamsには以下のようなメリットがあります。
- 少ないコード量で多くを実現できる — ごくわずかな行数のコードで、従来は多くの記述が必要だった処理を簡潔に実装できます。
- ラムダ式を活用できる — 使い捨ての関数とも言えるラムダ式を多用することで、直感的で読みやすいコードが書けます。
- 並列処理が簡単 — ParallelStreamを使えば、大規模データセットに対するマルチスレッド処理を非常に簡単に実現できます。
Streamsパイプラインの仕組み
一般的に、Streamsのパイプラインは次の3つの要素で構成されます。
- ソース(source) — データが流れ出る元となる部分
- 中間操作(intermediate operations) — 0個以上の操作
- 終端操作(terminal operation) — 最後に1つだけ実行される操作
ソースから要素のストリームが流れ出し、その要素に対してフィルタリング、ソート、マッピングなどの各種操作を順番に適用していきます。
最終的には、収集(collect)、集約(reduce)、またはその他の終端操作によって結果を取り出します。なお、終端操作として実行できるのは1つだけです。
ストリームのソース
ストリームのソースには、コレクション、リスト、セット、int・long・doubleの配列、文字列など、さまざまなデータ構造を使用できます。
ストリーム操作の種類
ストリーム操作には「中間操作」と「終端操作」の2種類があります。
- 中間操作 — filter、map、sortedなど。戻り値としてストリームを返すため、複数の中間操作を連結(チェーン)できます。
- 終端操作 — ストリームを受け取り、voidを返すか、あるいはリストへの集約などストリーム以外の結果を返します。
主な中間操作
- 中間操作は0個以上、いくつでも配置できます。
- 操作の順序は重要です。 特に大規模データセットでは、先にfilterで絞り込み、その後にsortやmapを行うのが効率的です。
- 非常に大きなデータセットを扱う場合は、ParallelStreamを使ってマルチスレッド処理を有効にしましょう。
代表的な中間操作には以下があります。
- anyMatch()
- distinct()
- filter()
- findFirst()
- flatMap()
- map()
- skip()
- sorted()
主な終端操作
終端操作は1つしか指定できません。
- forEach — 各要素に同じ処理を適用します。例えば各要素の出力など。
- collect — すべての要素をコレクション、リスト、配列などに保存します。
- その他の操作 — ストリームを単一の要約値へと集約(reduce)します。
集約系の関数の例:
- count()
- max()
- min()
- reduce()
Java Streamsコード例
それでは、これまで説明した概念を実際のコード例で確認していきましょう。
整数ストリーム(IntStream)
最初の例はシンプルな整数ストリームです。IntStreamクラスのrangeメソッドを使って、範囲内の整数ストリームを作成します。
forEachが終端操作となり、各要素を出力しています。
import java.io.IOException;
import java.util.stream.IntStream;
public class JavaStreams {
public static void main(String[] args) throws IOException {
IntStream
.range(1, 10)
.forEach(System.out::print);
System.out.println();
}
}
実行結果:
123456789
skip()を使った整数ストリーム
2つ目の例も整数ストリームですが、ここではskip()を追加しています。この場合、ストリームの最初の5つの要素がスキップされます。
そのため、6から9までの要素だけが出力されます。要素の出力にはシンプルなラムダ式を使用しています。
import java.io.IOException;
import java.util.stream.IntStream;
public class JavaStreams {
public static void main(String[] args) throws IOException {
IntStream
.range(1, 10)
.skip(5)
.forEach(x -> System.out.println(x));
System.out.println();
}
}
実行結果:
6
7
8
9
sum()で合計を求める整数ストリーム
3つ目の例でもIntStreamを使ってストリームを作成しますが、今回はその全体をprintln()の引数として渡しています。
出力されるのは、range(1, 5)で生成される数値(1, 2, 3, 4)の合計だけです。
import java.io.IOException;
import java.util.stream.IntStream;
public class JavaStreams {
public static void main(String[] args) throws IOException {
System.out.println(
IntStream
.range(1, 5)
.sum());
System.out.println();
}
}
実行結果:
10
Stream.of の使い方
次の例ではStream.ofメソッドを使用します。このメソッドは非常に便利で、整数、浮動小数点数、文字列、さらにはオブジェクトまでストリーム化できます。
この例では、まずアルファベット順にソートし、続いてfindFirst()で最初の要素を取得しています。そして、リストの最初の項目を出力します。
import java.io.IOException;
import java.util.stream.Stream;
public class JavaStreams {
public static void main(String[] args) throws IOException {
Stream.of("Ava", "Aneri", "Alberto")
.sorted()
.findFirst()
.ifPresent(System.out::println);
}
}
実行結果:
Alberto
配列からのストリーム生成、ソート・フィルタ・出力
次の例では、配列からストリームを作成し、ソート、フィルタリング、出力を行います。
ここでは、sで始まる項目のみをフィルタリングします。
ラムダ式は各名前を受け取るxを引数とし、sという文字で始まるものだけを通過させます。
その後ソートを行い、条件を満たした各項目を出力します。
import java.io.IOException;
import java.util.Arrays;
public class JavaStreams {
public static void main(String[] args) throws IOException {
String[] names = {"Al", "Ankit", "Kushal", "Brent", "Sarika", "amanda", "Hans", "Shivika", "Sarah"};
Arrays.stream(names)
.filter(x -> x.startsWith("S"))
.sorted()
.forEach(System.out::println);
}
}
実行結果:
Sarah
Sarika
Shivika
整数配列の平均値を求める
次に、int配列の各要素を2乗した値の平均を求める方法を見てみましょう。
ここではArrays.stream()メソッドで整数をストリーム化し、map()を使って各整数をその2乗に変換しています。
import java.util.Arrays;
public class JavaStreams {
public static void main(String[] args) {
Arrays.stream(new int[] {2, 4, 6, 8, 10})
.map(x -> x * x)
.average()
.ifPresent(System.out::println);
}
}
実行結果:
44.0
ポイント:average()は整数ではなくdouble型の値を返すことに注意してください。
リストからのストリーム生成、フィルタ・出力
この例では、リストからストリームを作成し、要素をフィルタリングして出力します。
map()メソッド内ですべての名前を小文字に変換している点に注目してください。
import java.util.Arrays;
import java.util.List;
public class JavaStreams {
public static void main(String[] args) {
List<String> people = Arrays.asList("Al", "Ankit", "Brent", "Sarika", "amanda", "Hans", "Shivika", "Sarah");
people
.stream()
.map(String::toLowerCase)
.filter(x -> x.startsWith("a"))
.forEach(System.out::println);
}
}
実行結果:
al
ankit
amanda
aで始まる3つの名前が、すべて小文字で出力されていることがわかります。
テキストファイルの行をストリーム処理してソート・フィルタ・出力
次の例では、テキストファイルの各行をストリームとして読み込み、ソート、フィルタリング、出力を行います。
以下のような内容を持つbands.txtというファイルがあると仮定しましょう。
Rolling Stones
Lady Gaga
Jackson Browne
Maroon 5
Arijit Singh
Elton John
John Mayer
CCR
Eagles
Pink
Aerosmith
Adele
Taylor Swift
Files.lines()を使ってストリームを作成すると、ファイルの各行が文字列としてストリームに流れます。
ストリームを取得したら、ソートを行い、13文字より長い項目だけをフィルタリングして、残った項目を出力します。
最後に、ファイルを閉じるためにbands.close()を呼び出します。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class JavaStreams {
public static void main(String[] args) throws IOException {
Stream<String> bands = Files.lines(Paths.get("bands.txt"));
bands
.sorted()
.filter(x -> x.length() > 13)
.forEach(System.out::println);
bands.close();
}
}
実行結果:
Jackson Browne
Rolling Stones
13文字を超えるバンド名が2つ取得できました。
テキストファイルの行をストリーム処理してリストに保存
この例では、上記と同じテキストファイルを使用します。
x.contains()という文字列メソッドを使って、jitという文字列を含む項目をフィルタリングします。
.collect()を使うことで、jitを含むすべての項目をリストに追加できます。
リストが完成したら、forEach演算子で各項目を出力します。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
public class JavaStreams {
public static void main(String[] args) throws IOException {
List<String> bands2 = Files.lines(Paths.get("bands.txt"))
.filter(x -> x.contains("jit"))
.collect(Collectors.toList());
bands2.forEach(x -> System.out.println(x));
}
}
実行結果:
Arijit Singh
CSVファイルの行をストリーム処理してカウント
この例では、CSVファイルの行をストリームとして読み込み、有効な行の数をカウントします。
以下のような内容のdata.txtというファイルがあるとします。
A,12,3.7
B,17,2.8
C,14,1.9
D,23,2.7
E
F,18,3.4
ここでE行にはデータがないため、この行をストリームから除外したいと思います。
以下のコードでは、まず各行を読み込み、カンマで分割して配列に変換します。これにより、各行が項目の配列になります。
その後、フィルタを適用して、3つの項目を持たない行を除外します。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class JavaStreams {
public static void main(String[] args) throws IOException {
Stream<String> rows1 = Files.lines(Paths.get("data.txt"));
int rowCount = (int)rows1
.map(x -> x.split(","))
.filter(x -> x.length == 3)
.count();
System.out.println(rowCount + " rows.");
rows1.close();
}
}
実行結果:
5 rows
集約(Reduction)— 合計の計算
この例では、集約(reduction)の使い方を紹介します。合計値への集約を行います。ここではStream.of()メソッドでdouble型のストリームを作成し、3つの異なる引数として3つのdouble値を定義して、reduce関数を使用します。
import java.util.stream.Stream;
public class JavaStreams {
public static void main(String[] args) {
double total = Stream.of(7.3, 1.5, 4.8)
.reduce(0.0, (Double a, Double b) -> a + b);
System.out.println("Total = " + total);
}
}
実行結果:
13.600000000000001
-
【Java】Character.charCount()メソッドの使い方を具体例付きで解説
Character.charCount()メソッドとはCharacter.charCount()メソッドは、指定された文字(Unicodeコードポイント)を表すために必要なchar値の数を判定するためのメソッドです。動作ルールはシンプルで、以下のように決まっています。指定されたコードポイントが0x10000以上の場合 → 2を返す指定されたコードポイントが0x10000未満の場合 → 1を返すこれは、Unicodeにおいて0x10000以上のコードポイント(いわゆる補助文字)が、サロゲートペアと呼ばれる2つのchar値(各16ビット)で表現されるためです。したがって、あるコードポイントが補助文
-
【初心者向け】JavaのArrayListメソッド完全ガイド|コード例つきで解説
この記事では、JavaのArrayListでよく使われる主要なメソッドを、実行結果つきのコード例とともにわかりやすく解説します。 ArrayListクラスは、サイズを自由に変更できる「可変長配列」です。リストの要素数が事前にわからない場合に特に便利で、Javaの開発現場で最も広く使われているコレクションの一つです。 ArrayListクラスは、java.utilパッケージに含まれています。 ArrayListの作成方法 ArrayListを作成して要素を追加するには、StringやIntegerなど扱いたい型を指定してArrayListオブジェクトをインスタンス化します。 なお、add()メソ