システムデザイン

【Java 8】10分で使えるラムダ式

*本記事は旧TechblogからCOLORSに統合した記事です。

前回の記事(※)では、関数型プログラミングの考え方をご紹介しました。
 今回の記事では、実際のサンプルコードを例に、関数型プログラミングの実現方法である「ラムダ式」についてご説明します。

※前回の記事「【Java 8】5分でわかる関数型プログラミング



目次

  1. メソッドの引数として処理を連携する方法
    1.1. 関数型インタフェース
    1.2. 関数型インタフェースの制約
  2. ラムダ式
    2.1. ラムダ式の文法
    2.2. メソッド参照
  3. ラムダ式の使用例
    3.1. 前回の記事に対応した使用例
    3.2. Optional型の利用
  4. まとめ


1. メソッドの引数として処理を連携する方法

1.1. 関数型インタフェース

Java8では、変数の値として処理の実装(メソッド)を受け取るための、変数の型「関数型インタフェース」というものが用意されています。


java8, 関数型インタフェース, 概念図
変数の型変数の値の例
String“あいう”
Integer12345
関数型インタフェース処理の実装(メソッド)

「関数型インタフェース」は、考え方としては、StringやIntegerなどと同様の、変数の型の一つです。
 「関数型インタフェース」型では、変数の値として、処理の実装(メソッド)を格納しています。
 また、その変数の値を定義する際に、「ラムダ式」という文法で処理(メソッド)を定義します。
(ラムダ式の詳細については後述します)

java8, 関数型インタフェース, 概念図, ラムダ式

1.2. 関数型インタフェースの制約

1.1.の説明では、「関数型インタフェース」と一言で説明しましたが、実際には、java.util.functionパッケージ内で、いくつかの種類の標準的な関数型インタフェースがあらかじめ提供されています。



関数型インタフェース抽象メソッド
Function<T, R>R apply(T t)
Predicate<T>boolean test(T t)
Consumer<T>void accept(T t)
Supplier<T>T get()

それぞれの関数型インタフェース毎に、「関数型インタフェース」型の変数の値として格納できる処理(メソッド)に制約があります。
 この制約というのは、「関数型インタフェース」型の変数の値として格納できる処理(メソッド)は、関数型インタフェースで定義されている抽象メソッドの実装でなければだめということです。



java8, 関数型インタフェース, 標準関数型インタフェース, 概念図, ラムダ式
関数型インタ
フェース
抽象メソッド変数の値として格納できる
処理(メソッド)
Function<T, R>R apply(T t)任意の引数を受け取り、
任意の戻り値を返す処理
Predicate<T>boolean test(T t)任意の引数を受け取り、
boolean型の戻り値を返す処理
Consumer<T>void accept(T t)任意の引数を受け取り、
戻り値を返さない処理
Supplier<T>T get()引数なしで、
任意の戻り値を返す処理

この「関数型インタフェース」を使用することで、メソッドの引数として処理を連携することができるようになります。
 次は、実際に関数型インタフェースに処理を定義する際に必要となる、ラムダ式の文法についてご説明します。



2. ラムダ式

2.1. ラムダ式の文法

ラムダ式の基本文法は以下の通りです。



(仮引数列) -> 処理の実装の本文
または
(仮引数列) -> { 処理の実装の本文(複数行)}

実際に、1.2. でご説明した標準の関数型インタフェースに、処理の実装を格納するサンプルコードを記載します。



  • Function<T, R>
    定義できる処理:任意の引数を受け取り、任意の値を返す処理。
    ※ Tには任意の引数の型、Rには任意の戻り値の型を定義する。以下では、T=String、R=Integerとしている。
Function<String, Integer> sampleFunc =
        // ラムダ式で処理の実装を定義する
        (String str) -> { // 任意の引数を受け取り
            System.out.println(str);
            return 0; // 任意の値を返す
        };
// 以下は上記と同じ意味です。
Function<String, Integer> sampleFunc =
        (str) -> { // 仮引数の型名は省略可能
            System.out.println(str);
            return 0;
        };

  • Predicate<T>
    定義できる処理:任意の引数を受け取り、booleanを返す処理。
    ※ Tには任意の引数の型を定義する。以下では、T=Stringとしている。
Predicate<String> samplePredicate = (str) -> { // 任意の引数を受け取り
    return "abc".equals(str); // booleanを返す
};
// 以下は上記と同じ意味です。
Predicate<String> samplePredicate = (str) -> "abc".equals(str); // 式で記載する場合、return は省略可能

  • Consumer<T>
    定義できる処理:任意の引数を受け取り、戻り値なしの処理。
    ※ Tには任意の引数の型を定義する。以下では、T=Stringとしている。
Consumer<String> sampleConsumer = (str) -> System.out.println(str); // 任意の引数を受け取り、戻り値なし

  • Supplier<T>
    定義できる処理:引数なしで、任意の値を返す処理。
    ※ Tには任意の戻り値の型を定義する。以下では、T=Integerとしている。
Supplier<Integer> sampleSupplier = () -> { // 引数なし
    return 1 + 2; // 任意の値を返す
};
// 以下は上記と同じ意味です。
Supplier<Integer> sampleSupplier = () -> 1 + 2; // 式で記載する場合、return は省略可能

2.2. メソッド参照

ここでは詳しい話は割愛しますが、関数型インタフェースの変数の値として、既存のメソッドを使用することもできます。



Consumer<String> sampleConsumer = System.out::println; // 「メソッド参照」という方法でも処理の定義が可能

3. ラムダ式の使用例

3.1. 前回の記事に対応した使用例

前回の記事(※)の2.2章では、例外処理での関数型プログラミングの利用例をご説明しましたが、今回のラムダ式を使用すると、以下のように実装できます。

※前回の記事「【Java 8】5分でわかる関数型プログラミング



/**
 * 「例外処理機能」が公開する例外処理インタフェース
 */
public interface SampleExceptionHandleInterface {
    /**
     * 例外処理メソッド
     *
     * @param exception 発生例外
     * @param exceptionHandlerConsumer 発生例外に適用するConsumer
     */
    void handleException(Exception exception, Consumer<Exception> exceptionHandlerConsumer);
/**
 * 「例外処理機能」が公開する例外処理インタフェースの処理実装
 */
public class SampleExceptionHandleImpl
        implements SampleExceptionHandleInterface {
    @Override
    public void handleException(Exception exception, Consumer<Exception> exceptionHandlerConsumer) {
        // 「ログ出力」を行う
        System.out.println(exception.getMessage());
        // 呼び出し元で定義した任意の処理を実行する
        exceptionHandlerConsumer.accept(exception);
    }
/**
 * 利用者
 */
public static void main(String[] args) {
    try {
        // 任意の業務ロジック
    } catch (Exception exception) {
        // 例外処理インタフェースを利用する
        SampleExceptionHandleInterface exceptionHandler = new SampleExceptionHandleImpl();
        // 実行したい独自の例外処理
        Consumer<Exception> gyomuAExceptionHandle = (e) -> {
            if (e.getMessage().contains("ECODE001")) {
                // 顧客へのメール送信
                SampleUtil.sendMail(e);
            }
        };
        // 例外処理インタフェースに、発生した例外をパラメータとして連携する
        exceptionHandler.handleException(exception, gyomuAExceptionHandle);
    }
}

3.2. Optional型の利用

Java8より登場した、Optional型も、ラムダ式が理解できれば便利に利用できる機能です。
 Optional型は、nullチェックを強要する型で、Optional型でラップされたオブジェクトがnullだった場合の挙動を定義できます。
 以下に使用例のサンプルコードを記載します。



// Optional型でラップされたオブジェクト
Optional<String> sampleOptional = Optional.of("abc");
  • Optionalオブジェクトのnullチェック例①
    メソッド:Optional#orElseThrow(Supplier exceptionSupplier)
    解説:Optionalの値が、nullの場合、指定されたSupplierによって作成された例外をスローする。
String sampleValue = sampleOptional.orElseThrow(
        () -> new RuntimeException("パラメータxxxに、nullは許容しません。")); // Optionalの値がnullの場合に実行するSupplierの処理

  • Optionalオブジェクトのnullチェック例②
    メソッド:Optional#orElseGet(Supplier other)
    解説:Optionalの値が、nullの場合、指定されたSupplierによって作成された値を代わりに返す。
String sampleValue = sampleOptional.orElseGet(
        () -> "nullだった場合の代わりの値"); // Optionalの値がnullの場合に実行するSupplierの処理

  • Optionalオブジェクトのnullチェック例③
    メソッド:Optional#ifPresent(Consumer consumer)
    解説:Optionalの値がnullでない場合は、指定されたコンシューマを実行する。
sampleOptional.ifPresent(
        (str) -> System.out.println(str)); // Optionalの値がnullでない場合に実行するConsumerの処理

4. まとめ

「10分で使えるラムダ式」は、ここまでとなります。
 今回記事では、関数型インタフェース、ラムダ式について、前回の記事の例も踏まえてご説明させていただきました。
 次回の記事では、「ラムダ式」を活用する「ストリームAPI」の解説を行う予定ですので合わせてお読みいただければ幸いです。