山崎屋の技術メモ

IT業界で働く中でテクノロジーを愛するSIerのシステムエンジニア👨‍💻 | AndroidとWebアプリの二刀流🧙‍♂️ | コードの裏にあるストーリーを綴るブログ執筆者✍️ | 日々進化するデジタル世界で学び続ける探究者🚀 | #TechLover #CodeArtisan、気になること、メモしておきたいことを書いていきます。

【Java】Stream を理解する その1(概要編)

こんにちは。

Java の Stream について概要から詳細まで全 4 回 で紹介していきます。

  • 【Java】Stream を理解する その1(概要編) ← この記事
  • 【Java】Stream を理解する その2(生成編)
  • 【Java】Stream を理解する その3(中間操作 編)
  • 【Java】Stream を理解する その4(終端操作 編)

公式ドキュメントはこちら。
Stream (Java Platform SE 8)

Java の基礎とラムダ式(「i -> i % 2 == 0」みたいなやつ)の意味を理解している前提で書いています。

Stream とは

2014 年 3 月にリリースされた Java 8 より導入された API。複数要素に対し、順番にある処理を行います。

「ある処理」の中では値の選別(filter)や型の変換(map)などを行うことができます。

Java 7 までだったら for(while) 文と if 文を組み合わせて作ったような処理を一文で書けます。

for や if が減るということはそれだけバグが混入する確率も減るわけですから、使える場所では積極的に使っていきましょう。

ただし、処理の中で発生した例外を呼び出し元に返したい時など Stream が苦手とする場面もありますので「for 文禁止」はやりすぎだと思います。

参照型を扱う「Stream」。プリミティブ型を扱う「DoubleStream」・「IntStream」・「LongStream」が提供されています。

Stream 構文

早速サンプルを見ていきましょう。ここではざっくり眺めるだけでも大丈夫ですが写経したほうが理解が早まります。

まずは for と if を使用した Java7 までの書き方です。

	public static void main(String[] args) {

		int[] array = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };

		for (int i = 0; i < array.length; i++) {

			if (array[i] % 2 == 0) {
				System.out.println(array[i]);
			}

		}

	}

10 ~ 19 までの数値の中で偶数のみをコンソールに出力するサンプルです。

実行結果。

10
12
14
16
18

これを Stream を用いて書くとこうなります。

	public static void main(String[] args) {

		int[] array = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };

		Arrays.stream(array)
		  .filter(i -> i % 2 == 0)
		  .forEach(i -> System.out.println(i));

	}

 
軽く解説すると「Arrays.stream(array)」で配列の要素を順番に処理するループを表し、「.filter(i -> i % 2 == 0)」で偶数のみを選別、「.forEach(i -> System.out.println(i))」で順番にコンソール出力しています。
 
見やすく行を分けましたが以下のように 1 行で書いても OK です。

	public static void main(String[] args) {

		int[] array = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };

		Arrays.stream(array).filter(i -> i % 2 == 0).forEach(i -> System.out.println(i));

	}

 
なお、「.」(ドット)でつなぐメソッドチェーンを用いず、文を分けることもできます(あまり見ませんが、)。いずれも結果は同じです。

	public static void main(String[] args) {

		int[] array = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };

		IntStream stream1 = Arrays.stream(array);
		IntStream stream2 = stream1.filter(i -> i % 2 == 0);
		stream2.forEach(i -> System.out.println(i));

	}

 
もう一つの例を示します。まずは Java7 までの書き方。

	public static void main(String[] args) {

		int[] array = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };

		String[] sArray = new String[array.length];

		for (int i = 0; i < array.length; i++) {

			sArray[i] = array[i] + "!";
		}

		System.out.println(Arrays.toString(sArray));

	}


10 ~ 19 までの数値を文字列に変換し「!」記号を加えてコンソール出力します。
 
実行結果。

[10!, 11!, 12!, 13!, 14!, 15!, 16!, 17!, 18!, 19!]

Stream で書くとこうなります。

	public static void main(String[] args) {

		int[] array = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };

		String[] sArray = Arrays.stream(array)
		  .mapToObj(i -> i + "!")
		  .toArray(i -> new String[i]);

		System.out.println(Arrays.toString(sArray));

	}

「Arrays.stream(array)」で配列の要素を順番に処理するループを表し、「.mapToObj(i -> i + "!")」で文字列に変換し「!」を付加、「.toArray(i -> new String[i])」で変換後の値を格納する配列を作っています。
 

Stream の生成と中間操作と終端操作

先ほどの 2 つのサンプルを見ると Stream は主に 3 つのパートに分かれているのに気が付くと思います。

最初のサンプルを抜粋しコメントを入れました。

		Arrays.stream(array) // Stream 生成
		  .filter(i -> i % 2 == 0) // 中間操作
		  .forEach(i -> System.out.println(i)); // 終端操作

Stream 生成メソッドおよび中間操作では Stream(DoubleStream、IntStream、LongStream の場合もあり)を返し、終端操作では Stream 以外の型のオブジェクトを返すか戻り値がないものがあります。

Stream の生成と終端操作は必ず 1 回ずつ必要で、中間操作は 0 回 ~ 複数回指定することができます。

中間操作が 2 回登場する例です。

	public static void main(String[] args) {

		int[] array = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };

		String[] sArray = Arrays.stream(array)
		  .filter(i -> i % 2 == 0)
		  .mapToObj(i -> i + "!")
		  .toArray(i -> new String[i]);

		System.out.println(Arrays.toString(sArray));

		// 実行結果:
		// [10!, 12!, 14!, 16!, 18!]
	}


このブログの Stream シリーズでは、この 3 つのパートをそれぞれ別の記事で詳しく掘り下げていきます。

なお、終端操作を行った Stream は再利用できません。利用しようとすると「IllegalStateException」が発生します。

2 回以上使いたい場合は都度生成してください。例外となるサンプルです。

	public static void main(String[] args) {

		int[] array = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };

		IntStream stream1 = Arrays.stream(array);
		stream1.forEach(i -> System.out.println(i));

		
		String[] sArray = stream1.mapToObj(i -> i + "!").toArray(i -> new String[i]); 
		// ↑↑↑ 一回終端操作を行った「stream1」を再度操作しようとしている。
                //      IllegalStateException 実行時エラーが発生する。

		System.out.println(sArray);

	}

今回のまとめ

Stream の概要と簡単な利用方法を紹介しました。次回は Stream の生成方法について掘り下げてお送りいたします。

それでは!

独習Java 新版

独習Java 新版

Effective Java 第3版

Effective Java 第3版

やさしいJava 第7版 (「やさしい」シリーズ)

やさしいJava 第7版 (「やさしい」シリーズ)