公開日:1/10/2021  更新日:6/13/2021

  • twitter
  • facebook
  • line

【Java】ラムダ式 と StreamAPI についての勉強メモ

目次

  1. はじめに
  2. ラムダ式
    1. ①インナークラスでの実装
    2. ②匿名クラスでの実装
    3. ③ラムダ式での実装
  3. 関数型インターフェース
  4. メソッド参照
  5. Stream API
    1. Stream の作成
    2. Stream に対する中間操作
    3. Stream に対する終端操作

はじめに

Java 8 から新たに、ラムダ式と StreamAPI という機能が追加されました。ラムダ式とはメソッドの引数に処理そのものを渡すことが出来るAPI記述に必須な記法です。StreamAPI とはコレクション操作を効率的に行えるように導入された機能です。従来の書き方のプログラミングは命令型プログラミングと呼ばれ、StreamAPIによる処理は関数型プログラミングと呼ばれます。本記事では、ネットや書籍で学んだ知識を備忘録としてまとめました。

ラムダ式

ラムダ式とは、以下のような特殊な書き方をする記法です。

インターフェース名 変数 = (引数) -> {処理}
  • 引数部分は、普通のメソッドの引数と同様の書き方。
  • メソッドの引数の型は省略可能。省略する際はすべての引数から省略する必要がある。
  • 引数が一つしかない場合は、引数の 丸括弧 () も省略することが出来る。
  • 処理が一つしかない場合は、return と処理の波括弧 {} を省略することができる。

理解を深めるために、従来の記法を徐々にラムダ式へとリファクタリングして進めていきます。
①インナークラス②匿名クラスラムダ式 の順で修正します。

使用クラス

Fig. 使用パッケージ

①インナークラスでの実装

まずは、インターフェイスの Sample クラスを用意します。

//Sample.java
package com.sample;

public interface Sample {
	public void execute();
}

次に、Factory クラスを用意します。こちらでは、インナークラス (InnerSampleImpl) のインスタンスを返却する generate メソッドを実装しています。背景として、InnerSampleImpl は外部からアクセス不可にするために、インナークラスとして実装されています。

//Factory.java
package com.sample;

public class Factory{
	public Sample generate() {
		return new Factory.InnerSampleImpl();
	}
	private class InnerSampleImpl implements Sample{
		@Override
		public void execute() {
			System.out.println("test");
		}
	}
}

最後にメインクラスを用意します。ポリモフィズムを使用して、InnerSampleImple 型のインスタンスをSample 型の変数 sample に代入しています。

//Main.java
package com.main;

import com.sample.Factory;
import com.sample.Sample;

public class Main {
	public static void main(String[] args) {
		Sample sample= new Factory().generate();
		sample.execute();
	}
}

②匿名クラスでの実装

Factory で定義したインナークラスの InnerSampleImpl を、匿名クラスにリファクタリングしました。このようにクラス名が削除されて、処理の中身のみ記述されたクラスを匿名クラスと呼びます。

//Factory.java
package com.sample;

public class Factory{
	public Sample generate() {
		return new Sample(){
			@Override
			public void execute() {
				System.out.println("test");
			}
		};
	}
}

③ラムダ式での実装

匿名クラスで記述された Factory をさらにラムダ式で簡略化すると以下のようになります。

//Factory.java
package com.sample;

public class Factory{
	public Sample generate() {
		return () ->{
				System.out.println("test");
		};
	}
}

ラムダ式は、戻り値に設定されたインターフェイスにメソッドが一つだけ定義されている場合にのみ使用可能です。そのような実装すべきメソッドが一つしかないインターフェイスを、関数型インターフェイスと呼びます(Java8以降)。この例では、Sample クラスにexecute メソッド 1つしか定義されていなかったためラムダ式が使用できたわけです。上の例だけだと、「匿名クラスの実装が数行省略できて何が嬉しいのか?」と思うかもしれません。しかし、「インタフェースの実装をいろいろな宣言をふまえて書く代わりに、処理の内容を表す式だけ書けばいい」という特性が、後に出てくる Stream API と組み合わさった時に、コードの可読性向上に大いに役立ちます。

関数型インターフェース

関数型インターフェース は自身で実装可能ですが、java.util.function では、あらかじめ必要になる関数型インターフェイスを提供してくれています。下記リンクから公式ドキュメントに飛べるので興味があれば覗いてください。

パッケージjava.util.function

よく使用される、java.util.function パッケージに属する代表的な関数型インターフェイスを紹介します。メソッドの引数と戻り値によって場合分けした表がこちらです。

引数あり 引数なし
戻り値あり Function , Predicate Supplier
戻り値なし Consumer ×
  1. Function
    1つの引数を受け取って結果を生成する。apply(Object) を関数メソッドに持つ関数型インタフェース。
  2. Supplier
    新しい(異なる)結果が返される必要なし。get() を関数メソッドに持つ関数型インタフェース。
  3. Consumer
    単一の入力引数を受け取って結果を返さないオペレーションを表します。accept(Object) を関数メソッドに持つ関数型インタフェース。
  4. Predicate
    1つの引数を受け取り boolean値を返す、test(Object) を関数メソッドに持つ関数型インタフェース。

また、java.lang パッケージには 引数と戻り値なしの 関数型インターフェイスである Runnable 等もあります。
関数型インターフェイスはたくさんの種類があるようですね。

関数型インターフェイス (Consumer) の使用例

import java.util.function.Consumer;

public class Main {

	public static void main(String[] args) {
		Consumer<String> sample= (String s) ->{System.out.println(s);};
		sample.accept("test");
		//testと出力
	}
}

メソッド参照

Java8では既に用意されているメソッドを代入して、関数型インターフェイスのメソッドをオーバーライドをすることが可能です。使用ルールとしては、代入するメソッドの引数と戻り値が、代入先の関数インターフェイスの引数と戻り値に対応している必要があります。メソッド参照の書き方は以下の表の通りです。

用法 書き方
インスタンスのメソッドを参照 {インスタンス名}:{メソッド名}
自分自身のインスタンスのメソッドを参照 this::{メソッド名}
staticメソッドを参照 {クラス名}::{メソッド名}

メソッド参照の使用例 1

//メソッド参照なし
Consumer<String> = s -> {System.out.println(s);}
s.accept("abc"); 
//メソッド参照あり
Consumer<String> = System.out::println;
s.accept("abc"); 

Consumer (引数1つで戻り値なしの 関数型インターフェイス) に 引数1つで戻り値なしの println() メソッドを参照代入している。accept メソッドを実行することで、System.out.println("abc") の結果が表示される。


メソッド参照の使用例 2

String s = "abc";
IntSupplier supplier = s::length;
System.out.println(supplier.getAsInt());

例2では、IntSupplier (引数なし,戻り値1つでint型 の関数型インターフェイス ) に、引数なし 戻り値が1つで int型 のlengthメソッドを参照代入している。IntSupplier に定義された getAsInt() を実行することで、"abc".length() の結果が返ってくる。

Stream API

Stream API は、大量データを逐次処理する「ストリーム処理」を効率的に記述するための手段として導入されました。Stream API は「作成」「中間操作」「終端操作」の3つの操作からできています。

操作 処理内容
作成 最初に一つ コレクションや配列などからStreamを作成する
中間操作 複数 Stream から Stream を作成する
終端操作 最後に一つ Stream からコレクションや配列へ変換したり、要素ごとに処理をしたり、要素を集計したりする

Stream API イメージ図

Fig. Stream API フロー図

① Stream の作成

List や Set からStream を作成するパターン

List<String> list = Arrays.asList("tanaka","kato","yamada");
Stream<String> stream = list.stream();
stream.forEach(System.out::println);

配列からStream を作成するパターン

//パターン1
String[] array = {"tanaka","kato","yamada"};
Stream<String> stream = Arrays.stream(array);
stream.forEach(System.out::println);
//パターン2
Stream<String> stream = Stream.of("tanaka","kato","yamada");
stream.forEach(System.out::println);

Map からStream を作成するパターン

Map<String, String> map = new HashMap<>();
map.put("1","tanaka");
map.put("2","kato");
map.put("3","yamada");

Stream<Entry<String, String>> stream = map.entrySet().stream();
stream.forEach(e -> System.out.println(e.getksy() + ":" + e.getValue()));

② Stream に対する中間操作

メソッド名にmapが入った、要素を置き換える系の中間操作。
メソッド例:map, mapToInt, mapToDouble, mapToLong, flatMap

List<Student> students =new ArrayList<>();
students.add(new Student("tanaka",30));
students.add(new Student("kato",60));
students.add(new Student("yamada",100));

Stream<Integer> stream = students.stream()
    //mapメソッドは Function (関数型インターフェイス) を引数に指定
    .map(s -> s.getScore());//ラムダ式
    .forEach(System.out::println);

要素を絞り込む中間操作
メソッド例:filter, limit ,distinct

List<Student> students =new ArrayList<>();
students.add(new Student("tanaka",30));
students.add(new Student("kato",60));
students.add(new Student("yamada",100));

Stream<Integer> stream = students.stream()
    //filterメソッドは Predicate (関数型インターフェイス) を引数に指定
    .filter(s -> s.getScore() > 50)//ラムダ式
    .forEach(s -> System.out.println(s.getName()));

要素を並び替える中間操作
メソッド例:sorted

List<Student> students =new ArrayList<>();
students.add(new Student("tanaka",30));
students.add(new Student("kato",60));
students.add(new Student("yamada",100));

Stream<Integer> stream = students.stream()
    //sorted メソッドは Comparator (関数型インターフェイス) を引数に設定
    .sorted((s1,s2) -> s2.getScore() - s1.getScore()) //ラムダ式で高い順に並び替え
    .forEach(s -> System.out.println(s.getName() + "" + s.getScore()));

③ Stream に対する終端操作

1.繰り返し処理を行う終端操作

メソッド名 処理内容 引数
forEach このストリームの各要素に対してアクションを実行する Consumer
List<String> list = Arrays.asList("tanaka","kato","yamada");
Stream<String> stream = list.stream();
stream.forEach(System.out::println);

2.結果をまとめて出す終端操作
メソッド例:collect, toArray, reduce

メソッド名 処理内容 引数 戻り値
collect 要素を走査し、結果を作成 Collector 具象クラス
toArray 全要素を配列に変換する なし OptionalObject[]
reduce 値を集約する BinaryOperator Optional
List<Student> students = new ArrayList<>();
students.add(new Student("tanaka",30));
students.add(new Student("kato",60));
students.add(new Student("yamada",100));

List<Student> newList = students.stream()
	.filter(s -> s.getScore() > 50)
	.collect(Collectors.toList());//Listにして返却

newList.forEach(s -> System.out.println(s.getName()));
//kato, yamada が出力

3.結果を一つだけ出す終端操作

メソッド名 処理内容 引数 戻り値
findFirst 先頭の要素を返す なし Optional
findAny いずれかの要素を返す なし Optional
min 最小の要素を返す Comparator Optional
max 最大の要素を返す Comparator Optional

Optional (java.util.Optional) を戻り値として返す。Optional とは、オブジェクトの参照がnull かもしれないことを明示的に表すことが出来るクラス。Stream が空であったとき、空を返すことができる。

Optional (Java Platform SE 8 )

//例1
List<Integer> list = Arrays.asList(200, 100, 300);
Optional<Integer> s = list.stream().min(Comparator.naturalOrder());
System.out.println(s.get());
//100 が出力

//例2:オブジェクトのフィールド値で比較するパターン
List<Student> students = new ArrayList<>();
students.add(new Student("tanaka",30));
students.add(new Student("kato",60));
students.add(new Student("yamada",90));

Comparator<Student> scoreComparator = Comparator.comparing(Student::getScore);
Optional<Student> res = students.stream()
    .max(scoreComparator);
System.out.println(res.get().getName());
//kato が出力

4.集計処理をおこなう終端操作

メソッド名 処理内容 戻り値
count 要素の個数を返す long
min 最小の要素を返す OptionalInt/OptionalDouble/OptionalLong
max 最大の要素を返す OptionalInt/OptionalDouble/OptionalLong
sum 合計値を返す OptionalInt/OptionalDouble/OptionalLong
average 平均値を返す OptionalDouble
List<Integer> list = Arrays.asList(200, 100, 300);
long s = list.stream().count();
System.out.println(s);
//3 が出力

戻る