Javaのラムダ式ってなんだ?

n-ozawan

皆さん、こんにちは。LP開発グループのn-ozawanです。
クリスマスですね。トナカイの鼻が赤いのは、非常に多くの毛細血管が集まっており、脳の温度調節をしているそうです。

本題です。
JavaのStream APIなどでよく見かける() -> {...}のような書き方、そういう物だと思って良く理解せずに使ってませんか?これはJava8で導入されたラムダ式と呼ばれる書き方で、今回はこのラムダ式がどういうものかを解説します。

ラムダ式

ラムダ式とは?

ラムダ式は、関数型インターフェースの抽象メソッドを匿名で実装するための記法です。難しいですね。順を追って説明します。

Javaはオブジェクト指向言語であり、原則、クラスを実装する必要があります。ラムダ式が登場する以前は、メソッド1つ定義するにしても、必ずクラスを定義していました。以下は無名クラスを使って定義したコードです。実装も面倒ですし、何よりも可読性が非常に悪いです。

Runnable runner = new Runnable() {
  @Override
  public void run() {
    System.out.println("Hello, World!");
  }
};

runner.run();

そこで登場したのがラムダ式でした。ラムダ式であれば以下のような記述ができます。

Runnable runner = () -> System.out.println("Hello, World!");
runner.run();

これは、これまで記述していたクラスを簡略化したものになります。処理内部ではクラスが存在することに注意が必要です。

関数型インターフェースとは?

関数型インターフェースとは、抽象メソッドを1つだけ持つインターフェースのことです。ラムダ式は、抽象メソッドを1つだけ持つインターフェースでのみ、簡略化した記述ができるようになります。

interface FunctionalInterfaceExample {
  void run(String message);
}

FunctionalInterfaceExample example = (message) -> System.out.println(message);
example.run("Hello, Functional Interface!");

もし、抽象メソッドが2つ以上ある場合、ビルド時にエラーになります。

interface FunctionalInterfaceExample {
  void run(String message);
  void hoge();
}

FunctionalInterfaceExample example = (message) -> System.out.println(message);
example.run("Hello, Functional Interface!");

// main.java:24: error: incompatible types: FunctionalInterfaceExample is not a functional interface
//     FunctionalInterfaceExample example = (message) -> {
//                                          ^
//     multiple non-overriding abstract methods found in interface FunctionalInterfaceExample
// 1 error
// error: compilation failed

関数型インターフェースを定義する場合は、@FunctionalInterfaceあのテーションを付けて、明示的に関数型インターフェースであることを示すのが一般的です。

@FunctionalInterface
interface FunctionalInterfaceExample {
  void run(String message);
}

標準の関数型インターフェース

いちいち関数型インターフェースを定義するまでもない、その場限りの処理をラムダ式で記述したい場合があります。Javaは、汎用的に使える関数型インターフェースを用意しています。

Function<T,R>

Function<T,R>は、1つの引数と返却値を持つ関数型インターフェースです。一般的に受け取った値を変換して返す処理などに使われます。

Function<String, Integer> stringLengthFunction = (str) -> str.length() * 10;
int length = stringLengthFunction.apply("Hello");
System.out.println("Length: " + length);    // Outputs: Length: 50

Consumer<T>

Consumer<T>は、1つの引数を受け取り、返却値を持たない関数型インターフェースです。一般的に受け取った値の処理などに使われます。

Consumer<String> printer = (message) -> System.out.println(message);
printer.accept("Hello, Consumer!");     // Outputs: Hello, Consumer!

Predicate<T>

Predicate<T>は、1の引数を受け取り、booleanを返却する関数型インターフェースです。一般的に受け取った値に対して判定を行う処理などに使われます。

Predicate<String> isHello = (str) -> str != null && str.equals("hello");
System.out.println(isHello.test("hello"));  // Outputs: true
System.out.println(isHello.test(null));     // Outputs: false

Supplier<T>

Supplier<T>は、引数なしで、何かしらの結果を返却する関数型インターフェースです。一般的に乱数値の生成や初期化処理などに使われます。

Supplier<Double> randomSupplier = () -> Math.random();
System.out.println("Random number: " + randomSupplier.get());

まとめ

ラムダ式は、関数型インターフェースを簡潔に実装し、振る舞いを値として扱うための強力な機能です。ラムダ式を使いこなすことで、コードがシンプルになり、保守性も向上します。

ではまた。

Recommendおすすめブログ