CodeGym /コース /JAVA 25 SELF /無名クラス: ラムダとの違いと例

無名クラス: ラムダとの違いと例

JAVA 25 SELF
レベル 48 , レッスン 4
使用可能

1. 無名クラスを掘り下げる

無名クラスとは、使用箇所でその場で作成される、名前のないサブクラスまたはインターフェースの実装です。ラムダ登場以前(Java 8)は、インターフェースや抽象クラスを「使い切り」で実装する最も便利な方法でした。

定番の例:

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("無名クラスからこんにちは!");
    }
};
r.run();

ここでは、Runnable インターフェースを別ファイルやクラス名なしで、その場で宣言・実装しています。このような実装は、イベントハンドラやコンパレータ、スレッドなど、手早く「振る舞い」を差し込みたい場面でよく使われていました。

ラムダが「その場で書ける式」だとすれば、無名クラスは「名前のない小さな役者」。端役を演じたら消えていきます。

2. ラムダ式との比較

構文

無名クラス:

Comparator<String> comp = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
};

ラムダ式:

Comparator<String> comp = (a, b) -> a.length() - b.length();

違いは明らかです。ラムダの方がコンパクトで、処理が単純なら型やメソッド名を明示したり、余分な波かっこを書く必要がありません。

機能面

  • 無名クラス は完全なオブジェクトです。フィールドや追加メソッドを宣言でき、Object のメソッド(toStringequals など)をオーバーライドできます。
  • ラムダ式 は関数型インターフェースの1つの抽象メソッドの実装です。内部で独自のフィールドや追加メソッドを宣言することはできません。

どちらを選ぶべきか?

  • ラムダ — 関数型インターフェースの1メソッドを手短に実装したいとき。
  • 無名クラス — 次のような場合:
    • 複数のメソッドを実装したい(例: 抽象クラス);
    • 状態を保つためのフィールドを宣言したい;
    • Object のメソッド(例: toString)をオーバーライドしたい;
    • 継承/アクセスの特性を使いたい(例: スーパークラスの protected メンバーにアクセス)。

3. スコープとキーワード this

ここはよくある落とし穴です:

  • 無名クラス 内では、this は無名クラスのインスタンスを指します;
  • ラムダ式 内では、this はラムダを宣言した外側のクラスを指します。

例: 挙動を比較

public class Outer {
    String name = "外側のクラス";

    void test() {
        Runnable anon = new Runnable() {
            String name = "無名クラス";
            @Override
            public void run() {
                System.out.println(this.name); // "無名クラス"
            }
        };
        Runnable lambda = () -> System.out.println(this.name); // "外側のクラス"

        anon.run();
        lambda.run();
    }
}

出力:

無名クラス
外側のクラス

無名クラスでは this は無名クラス自身(そのフィールド name)を参照します。ラムダでは thisOuter を指します。

4. 無名クラスを使うべきとき

複数のメソッドを実装する必要がある場合

ラムダは関数型インターフェース(抽象メソッドがちょうど1つ)にしか使えません。インターフェースや抽象クラスで複数メソッドの実装が必要な場合は、無名クラスが必要です。

abstract class Animal {
    abstract void say();
    abstract void jump();
}

Animal cat = new Animal() {
    @Override
    void say() {
        System.out.println("ニャー!");
    }
    @Override
    void jump() {
        System.out.println("ピョン!");
    }
};

状態(フィールド)を保持したい場合

Runnable r = new Runnable() {
    int counter = 0;
    @Override
    public void run() {
        counter++;
        System.out.println("呼び出し " + counter + " 回");
    }
};
r.run(); // 1 回呼び出し
r.run(); // 2 回呼び出し

Object のメソッドをオーバーライドする必要がある場合

Comparator<String> comp = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
    @Override
    public String toString() {
        return "文字列の長さによるコンパレータ";
    }
};
System.out.println(comp); // 文字列の長さによるコンパレータ

5. 例: Comparator と Runnable — ラムダ vs 無名クラス

文字列を長さでソート

無名クラス:

List<String> words = Arrays.asList("ネコ", "ゾウ", "ネズミ", "トラ");
words.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});
System.out.println(words);

ラムダ式:

List<String> words = Arrays.asList("ネコ", "ゾウ", "ネズミ", "トラ");
words.sort((a, b) -> a.length() - b.length());
System.out.println(words);

結果は同じですが、ラムダの方が短く、読みやすいコードになります。

Runnable: スレッドの起動

無名クラス:

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("無名クラス経由のスレッド");
    }
});
t1.start();

ラムダ式:

Thread t2 = new Thread(() -> System.out.println("ラムダ経由のスレッド"));
t2.start();

フィールドを持つ無名クラス

Runnable r = new Runnable() {
    int count = 0;
    @Override
    public void run() {
        count++;
        System.out.println("呼び出し " + count + " 回");
    }
};
r.run(); // 1 回呼び出し
r.run(); // 2 回呼び出し

ラムダではこれはできません — フィールドを宣言できないためです。

6. 注意点: スコープ、変数、そして final

無名クラスでもラムダ式でも、外側メソッドのローカル変数は final または「実質的に final」(初期化後に変更されない)である場合にのみ使用できます。ただし名前に関して違いがあります:

  • 無名クラスでは、外側スコープと同じ名前の変数を宣言できます(シャドーイング)。
  • ラムダではできません。同名の外側変数と衝突してはいけません。

例:

int x = 10;
Runnable r = new Runnable() {
    @Override
    public void run() {
        int x = 20; // OK: 外側の変数をシャドーイング
        System.out.println(x); // 20
    }
};
r.run();

Runnable l = () -> {
    // int x = 30; // コンパイルエラー: 変数はすでに定義されています
    System.out.println(x); // 10
};
l.run();

7. いつラムダが最適で、いつ無名クラスが不可欠か?

次のような場合はラムダを選びましょう:

  • 関数型インターフェース用の短い関数を実装したい;
  • 状態を保持する必要がない;
  • Object のメソッドをオーバーライドする必要がない;
  • 「ここで今すぐ」使う簡単な実装である。

次のような場合は無名クラスが必要です:

  • 複数メソッドを持つインターフェースや抽象クラスを実装したい;
  • フィールドや追加メソッドを宣言したい;
  • toStringequalshashCode をオーバーライドしたい;
  • スーパークラスの protected メンバーにアクセスしたい。

8. 実践: 例で比較

課題1: Predicate でリストをフィルタ

無名クラス:

List<String> animals = Arrays.asList("ネコ", "ゾウ", "ネズミ", "トラ");
animals.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.length() < 4;
    }
});
System.out.println(animals); // [ゾウ, ネズミ, トラ]

ラムダ式:

List<String> animals = Arrays.asList("ネコ", "ゾウ", "ネズミ", "トラ");
animals.removeIf(s -> s.length() < 4);
System.out.println(animals); // [ゾウ, ネズミ, トラ]

課題2: this のスコープを比較

public class Demo {
    String name = "Demo";

    void check() {
        Runnable anon = new Runnable() {
            String name = "Anon";
            @Override
            public void run() {
                System.out.println(this.name); // "Anon"
            }
        };

        Runnable lambda = () -> System.out.println(this.name); // "Demo"

        anon.run();
        lambda.run();
    }

    public static void main(String[] args) {
        new Demo().check();
    }
}

9. 無名クラスとラムダを使うときの典型的なミス

エラー №1: ラムダで複数メソッドを実装できると期待してしまう。 ラムダは関数型インターフェース(抽象メソッドが1つ)にしか使えません。メソッドが複数あるなら無名クラスを使ってください。

エラー №2: this のスコープの取り違え。 ラムダでは this は外側のクラス、無名クラスでは無名クラス自身を指します。これにより、意図しないフィールドや値を参照してしまうことがあります。

エラー №3: ラムダでフィールドを宣言しようとする。 ラムダでは独自のフィールドを宣言できません — 使えるのは外側コンテキストの変数(final/「実質的 final」)のみです。状態が必要なら無名クラスを使いましょう。

エラー №4: 変数のシャドーイング。 無名クラスでは外側と同名のローカル変数を宣言できます(シャドーイング)。ラムダではできません — コンパイラエラーになります。

エラー №5: ラムダのロジックが複雑すぎる。 ラムダ本体が 3〜5 行を超えるようなら可読性が落ちます。コードを別メソッドに切り出すか、(状態や複数メソッドが必要なら)無名クラスを使いましょう。

1
アンケート/クイズ
ラムダ式、レベル 48、レッスン 4
使用不可
ラムダ式
ラムダ式
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION