ラムダ式は、Java 8で導入され、単一メソッドのインターフェース(関数型インターフェース)を実装するためのより簡潔な構文を提供します。
関数型インターフェースとは、ちょうど1つの抽象メソッドを持つインターフェースのことです。例:
@FunctionalInterface interface MyAction { void perform(String s); }
ラムダ式は、このようなインターフェースを実装することを可能にします:
MyAction action = (s) -> System.out.println(s); action.perform("Hello lambda!");
ラムダ式を使用する際、コンパイラはどのインターフェースが実装されているかを自動的に理解します(ターゲットタイプ)。ラムダはコレクションと共に頻繁に使用されます:
List<String> list = Arrays.asList("one", "two", "three"); list.forEach(s -> System.out.println(s));
質問: ラムダ式は外部クラスの非静的フィールドやメソッドを参照できますか?その際の制限は何ですか?
回答: ラムダ式は外部クラスのフィールドやメソッドを参照できますが、外部メソッドからローカル変数を使用する場合、それらの変数はfinalまたはeffectively final(つまり最初の代入以降は変更されない)である必要があります。例:
void doIt() { int x = 42; Runnable r = () -> System.out.println(x); // xはeffectively finalでなければならない }
xを宣言後に変更すると、コンパイルエラーが発生します。
物語
メソッド内でラムダを使用した際に、外部のローカル変数を変更しようとした結果、「ラムダ式内で使用される変数はfinalまたはeffectively finalであるべき」というコンパイルエラーが発生しました。開発者たちは原因を探すのに多くの時間を費やしましたが、この制限を思い出すまででした。
物語
あるプロジェクトではラムダ用に独自のインターフェースを使用しましたが、
@FunctionalInterfaceの注釈を付けることを忘れていました。リファクタリング後、インターフェースに2つ目のメソッドを追加したため、プロジェクトはコンパイルできなくなりました。これにより、トラブルシューティングが難しい予期しないエラーが発生しました。
物語
ラムダ式を含むフィールドを持つオブジェクトをシリアライズしようとした際、シリアライズ/デシリアライズが正しく機能せず、ラムダはデフォルトではシリアライズされないことが明らかになりました。ラムダが一貫性のない依存関係を持つ場合、ネットワークを介して送信されるとエラーが発生することを忘れないでください。