おすすめアイテムをいくつか紹介

広告ブロックがONになっていると表示されないことがあります。

【初心者向け】Javaのラムダ式をわかりやすく解説!サンプルソースから使い方、注意点まで

お疲れ様です。
はるさらと申します。

今回は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にはよく使われる
関数型インターフェースがいくつかあります。

インターフェースメソッド説明
Runnablerun()引数・戻り値なし
Consumer<T>accept(T)引数あり、戻り値なし
(処理を消費)
Supplier<T>get()引数なし、戻り値あり
(値を供給)
Function<T,R>apply(T)引数あり、戻り値あり
(関数型処理)
Predicate<T>test(T)条件を判定してbooleanを返す

上記を用いたいくつかの使用例を記載します。
例に記載されていない内容でも、
仕組みを理解して流用すれば対応することが可能です。

// Consumer: 引数を受け取って処理する
Consumer&lt;String> printer = msg -> System.out.println(msg);
printer.accept("こんにちは!");

// Function: 値を変換する
Function&lt;Integer, Integer> square = x -> x * x;
System.out.println(square.apply(5)); // 25

// Predicate: 条件をチェックする
Predicate&lt;String> isEmpty = s -> s.isEmpty();
System.out.println(isEmpty.test("")); // true


◆ ラムダ式が活躍するシーン

ラムダ式の真価は、
以下のような場面で発揮されます。

・Stream API を使ったデータ処理

List&lt;String> names = Arrays.asList("Yuki", "Sora", "Hana");

List&lt;String> filtered = names.stream()
    .filter(name -> name.startsWith("S"))
    .collect(Collectors.toList());

・イベントリスナーやコールバック処理

button.addActionListener(e -> System.out.println("クリックされました"));

・一時的な処理の渡し方

Function&lt;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&lt;String> printer1 = msg -> System.out.println(msg);
Consumer&lt;String> printer2 = System.out::println; // メソッド参照

見やすく、重複のない記述が可能です。


◆ ラムダ式が向かないケース

ラムダ式は便利ですが、万能ではありません
以下のような処理には向いていません。

  • 複雑な条件分岐やループ処理がある場合
  • 大規模・再利用性の高いビジネスロジック
  • 単体テストを書きたい処理

そのような場面では、通常のメソッドやクラスで
記述した方がメンテナンス性が高くなります


◆ ラムダ式と変数スコープに注意!

ラムダ式の中で外部の変数を使う場合、
「実質的に final」な変数しか使えません。

int num = 10;
Runnable r = () -> System.out.println(num); // OK

// num = 20; // NG:ラムダ内で使っている変数は変更できない

これは**「クロージャ」**の特性によるもので、
ラムダ式が定義されたスコープの変数を保持するためです。


◆ まとめ:ラムダ式はシンプルさが強力な武器!ただし使いどころが大事

ラムダ式は、Javaにおける「関数型インターフェース」の実装を、
シンプルかつ可読性高く記述するための非常に便利な構文です。
匿名クラスと比べてコードがすっきりまとまり、
イベント処理や一時的な処理の記述に特に威力を発揮します。

ただし、すべての場面でラムダ式が最適というわけではありません
複雑な処理や再利用が求められる場面では、
従来どおりのクラスやメソッドでの実装のほうが
保守性に優れることもあります。

ラムダ式の特徴と使いどころを理解し、
適材適所で活用することで、
より読みやすく、洗練されたJavaコードを書けるようになります。

どなたかのお役に立てば幸いです。
それではまたー!!