お疲れ様です。
はるさらと申します。
今回はJava 8以降で登場した「ラムダ式(lambda expression)」について
経験の浅い方でも伝わるように記事にしていきます。
- そもそもラムダ式ってなに?
- どうやって使うの?
- 匿名クラスとどう違うの?
この記事では上記のような疑問も含めて
基本構文・具体例・使いどころ・注意点などを、
経験の浅い方にも伝わるように解説していきます!
◆ ラムダ式とは? →一言でいうと「短い関数」
ラムダ式(lambda expression)とは、
「関数型インターフェース」の実装を、
簡潔に記述できる構文のことです。
そもそも「関数型インターフェース」とは?
Javaでは通常、クラスに implements
して
インターフェースを実装しますが、
「1つだけ抽象メソッドを持つインターフェース」のことを、
関数型インターフェース(Functional Interface)と呼びます。
「1つだけ抽象メソッドを持つインターフェース」の意味がいまいちわからない方のために、次の章で説明しています。混乱してしまってもまだ諦めないで下さい!!
Java 8以降、関数型インターフェースには
@FunctionalInterface
アノテーションが
付けられるようになりました。
以下は関数型インタフェースの例になります。
@FunctionalInterface
public interface Greeting {
void sayHello(String name);
}
この Greeting
メソッド をラムダ式で書くと、
こんなにシンプルになります。
スッキリして見えますよね。これがラムダ式になります。
Greeting g = name -> System.out.println("こんにちは、" + name);
g.sayHello("はるさら");
~余談~「1つだけ抽象メソッドを持つインターフェース」について
まずはインターフェースについてです。
インターフェースは、「こういう操作をできますよ」とだけ
約束(ルール)を書く設計図のようなものです。
たとえば、リモコンのインターフェースを考えてみてください。
interface RemoteControl {
void turnOn();
void turnOff();
}
このインターフェースは、
「リモコンは turnOn と turnOff の機能を持ってるよ」
と言っているだけです。中身はまだありません。
この中の void turnOn();
や void turnOff();
が、
「抽象メソッド」です。
中身の処理が書かれていない、名前だけのメソッドです。
◆抽象メソッド =「やることの名前だけ決まってて、中身は空っぽ」
void sayHello(); // ← これは「抽象メソッド」
前章でいうこの「sayHelloするよ!」っていう名前だけが決まってて、
どうやるかはまだ決まっていないものを「抽象メソッド」と呼びます。
◆それを踏まえて「1つだけ抽象メソッドを持つインターフェース」って?
たとえば、こんなインターフェースです:
interface Greeting {
void sayHello(String name); // ← これ1つだけ!
}
このように、抽象メソッドが1つしかないインターフェースを、
Javaでは「関数型インターフェース(Functional Interface)」と呼びます。
そして、このタイプのインターフェースには
「ラムダ式」が使えるんです!
◆ たとえ話:ボタンを押すと何かする「ボタンの設定」
たとえば、下記のようなコードがあったとします。
🔸 昔の書き方(匿名クラス)
Button btn = new Button();
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick() {
System.out.println("ボタンが押されました");
}
});
→ print文を発行するだけなのに
数行記載しなければなりません。
🔸 ラムダ式を使うとこうなります!
btn.setOnClickListener(() -> System.out.println("ボタンが押されました"));
この OnClickListener
というインターフェースは、
「onClick」だけを持つインターフェースなんです。
つまり:
- 「ボタンが押されたら何をする?」という1つのルール
- 中身はラムダ式で1行で書ける
◆まとめ:簡単に言うと・・・
・インターフェース =「ルールだけ書かれた設計図」
・抽象メソッド =「名前だけ決まってて、中身がないメソッド」
・関数型インターフェース =「抽象メソッドが1つだけのインターフェース」
・ラムダ式 = そういう1つのルールにサッと処理を渡せる書き方
◆ ラムダ式の基本構文
ラムダ式の書き方は以下のようになります。
(引数) -> { 処理内容 }
ラムダ式の基本例
(a, b) -> a + b
この例は、2つの引数を受け取り、
合計を返すラムダ式です。
ラムダ式の省略記法
1行で処理が完結する場合は {}
や
return
を省略できます。
a -> System.out.println(a)
引数が1つなら、()
も省略可能です。
◆ 匿名クラスとラムダ式の比較
たとえ話の章でも記載しましたが、
Java 8以前では、「関数を渡す」ような処理をするには、
匿名クラスを使っていました。
匿名クラスの例(従来の記法)
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello!");
}
};
r.run();
同じ処理をラムダ式で
Runnable r = () -> System.out.println("Hello!");
r.run();
たった1行で同じことができます。
これがラムダ式の強みです!
◆ よく使われる関数型インターフェースとラムダ式の組み合わせ
ラムダ式を使うには、
対象が関数型インターフェースであることが前提です。
Javaにはよく使われる
関数型インターフェースがいくつかあります。
インターフェース | メソッド | 説明 |
---|---|---|
Runnable | run() | 引数・戻り値なし |
Consumer<T> | accept(T) | 引数あり、戻り値なし (処理を消費) |
Supplier<T> | get() | 引数なし、戻り値あり (値を供給) |
Function<T,R> | apply(T) | 引数あり、戻り値あり (関数型処理) |
Predicate<T> | test(T) | 条件を判定してbooleanを返す |
上記を用いたいくつかの使用例を記載します。
例に記載されていない内容でも、
仕組みを理解して流用すれば対応することが可能です。
// Consumer: 引数を受け取って処理する
Consumer<String> printer = msg -> System.out.println(msg);
printer.accept("こんにちは!");
// Function: 値を変換する
Function<Integer, Integer> square = x -> x * x;
System.out.println(square.apply(5)); // 25
// Predicate: 条件をチェックする
Predicate<String> isEmpty = s -> s.isEmpty();
System.out.println(isEmpty.test("")); // true
◆ ラムダ式が活躍するシーン
ラムダ式の真価は、
以下のような場面で発揮されます。
・Stream API を使ったデータ処理
List<String> names = Arrays.asList("Yuki", "Sora", "Hana");
List<String> filtered = names.stream()
.filter(name -> name.startsWith("S"))
.collect(Collectors.toList());
・イベントリスナーやコールバック処理
button.addActionListener(e -> System.out.println("クリックされました"));
・一時的な処理の渡し方
Function<Integer, Integer> calcTax = price -> (int)(price * 1.1);
System.out.println(calcTax.apply(100)); // 110
◆ ラムダ式と匿名クラスの違いまとめ
比較項目 | 匿名クラス | ラムダ式 |
---|---|---|
可読性 | 冗長になりやすい | シンプルで読みやすい |
this の意味 | 匿名クラス自身を指す | 外側のクラスを指す |
処理の複雑さ | 複雑な処理でも書ける | 単純な処理に向いている |
コードの量 | 多くなる | 少なく済む |
this
の違い例
public class Example {
String name = "Outer";
void run() {
Runnable r1 = new Runnable() {
String name = "Inner";
public void run() {
System.out.println(this.name); // Inner
}
};
Runnable r2 = () -> {
System.out.println(this.name); // Outer
};
r1.run();
r2.run();
}
}
Runnable r1 = new Runnable() { … }(匿名クラス)
ここで this は、匿名クラス自身(new Runnable()で作った無名クラス) を指します。
よって、this.name は “Inner” を参照して “Inner” と表示されます。
Runnable r2 = () -> { … }(ラムダ式)
ラムダ式では this は、囲っている外側のクラス(この場合は Example クラス) を指します。
Example クラスの name は “Outer” なので、this.name は “Outer” を参照します。
ラムダ式の this は、外側のクラスのインスタンスを指す。
匿名クラスは
**「別の部屋(クラス)」**をその場で建てて作業してるイメージ。
→ this
はその部屋自身。
ラムダ式は
**「そのまま外の部屋(元のクラス)」**で作業しているイメージ。
→ this
は元の部屋(クラス)。
◆ メソッド参照でさらに簡潔に
ラムダ式はさらに短く書ける場面では、
**「メソッド参照」**が使えます。
Consumer<String> printer1 = msg -> System.out.println(msg);
Consumer<String> printer2 = System.out::println; // メソッド参照
見やすく、重複のない記述が可能です。
◆ ラムダ式が向かないケース
ラムダ式は便利ですが、万能ではありません。
以下のような処理には向いていません。
- 複雑な条件分岐やループ処理がある場合
- 大規模・再利用性の高いビジネスロジック
- 単体テストを書きたい処理
そのような場面では、通常のメソッドやクラスで
記述した方がメンテナンス性が高くなります。
◆ ラムダ式と変数スコープに注意!
ラムダ式の中で外部の変数を使う場合、
「実質的に final」な変数しか使えません。
int num = 10;
Runnable r = () -> System.out.println(num); // OK
// num = 20; // NG:ラムダ内で使っている変数は変更できない
これは**「クロージャ」**の特性によるもので、
ラムダ式が定義されたスコープの変数を保持するためです。
◆ まとめ:ラムダ式はシンプルさが強力な武器!ただし使いどころが大事
ラムダ式は、Javaにおける「関数型インターフェース」の実装を、
シンプルかつ可読性高く記述するための非常に便利な構文です。
匿名クラスと比べてコードがすっきりまとまり、
イベント処理や一時的な処理の記述に特に威力を発揮します。
ただし、すべての場面でラムダ式が最適というわけではありません。
複雑な処理や再利用が求められる場面では、
従来どおりのクラスやメソッドでの実装のほうが
保守性に優れることもあります。
ラムダ式の特徴と使いどころを理解し、
適材適所で活用することで、
より読みやすく、洗練されたJavaコードを書けるようになります。
どなたかのお役に立てば幸いです。
それではまたー!!