【プログラミングしてみよう49】JavaのInner Class(内部クラス)入門

Java

前回はequalsとhashCodeを正しくオーバーライドする方法を学びました。

今回は「内部クラス(Inner Class)」について解説します。 Javaではクラスの中に別のクラスを定義できます。内部クラスを使うことで、外側のクラスと密接に関連するクラスをひとまとめにして管理できるようになります。

内部クラスの種類

Javaの内部クラスには4種類あります。

メンバ内部クラス  // クラスのメンバとして定義。外側クラスのインスタンスと紐づく
staticネストクラス // staticとして定義。外側クラスのインスタンスなしで使える
ローカルクラス    // メソッドの中で定義するクラス
無名クラス       // 名前のない使い捨てクラス。インターフェースの実装などに使う

今回は特によく使われる「メンバ内部クラス」「staticネストクラス」「無名クラス」を解説します。

サンプルコード(メンバ内部クラス)

ファイル作成

「Java37」フォルダに Outer.javaInnerClassTest.java という名前でファイルを作成します

コード入力

Outer.java に以下のコードを入力します。

public class Outer {
    private String message = "外側クラスのフィールド";

    // メンバ内部クラス
    class Inner {
        void show() {
            // 外側クラスのprivateフィールドにアクセスできる
            System.out.println("Inner から: " + message);
        }
    }

    // staticネストクラス
    static class StaticNested {
        void show() {
            // staticなので外側クラスのインスタンスフィールドにはアクセスできない
            System.out.println("StaticNested から: こんにちは");
        }
    }

    void createInner() {
        Inner inner = new Inner();
        inner.show();
    }
}

InnerClassTest.java に以下のコードを入力します。

public class InnerClassTest {
    public static void main(String[] args) {
        // メンバ内部クラスは外側クラスのインスタンスが必要
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.show();

        // 外側クラスのメソッド経由でも使える
        outer.createInner();

        // staticネストクラスは外側クラスのインスタンス不要
        Outer.StaticNested nested = new Outer.StaticNested();
        nested.show();
    }
}

コンパイル

javac Outer.java InnerClassTest.java

実行

java InnerClassTest

実行結果は次のようになります。

Inner から: 外側クラスのフィールド
Inner から: 外側クラスのフィールド
StaticNested から: こんにちは

サンプルコード(staticネストクラスと無名クラス)

実際の開発でよく使われる無名クラスのパターンも見てみましょう。

ファイル作成

「Java37」フォルダに AnonymousTest.java という名前でファイルを作成します

コード入力

以下のコードを入力します。

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class AnonymousTest {

    // Builderパターンをstaticネストクラスで実装
    static class Person {
        private final String name;
        private final int age;
        private final String email;

        private Person(Builder builder) {
            this.name = builder.name;
            this.age = builder.age;
            this.email = builder.email;
        }

        @Override
        public String toString() {
            return "Person{name='" + name + "', age=" + age + ", email='" + email + "'}";
        }

        // staticネストクラスとしてBuilderを定義
        static class Builder {
            private String name;
            private int age;
            private String email;

            Builder name(String name) { this.name = name; return this; }
            Builder age(int age) { this.age = age; return this; }
            Builder email(String email) { this.email = email; return this; }
            Person build() { return new Person(this); }
        }
    }

    public static void main(String[] args) {
        // Builderパターンでインスタンスを作成
        Person person = new Person.Builder()
                .name("ぱんまる")
                .age(25)
                .email("panmaru@example.com")
                .build();
        System.out.println("Builderパターン: " + person);

        // 無名クラスでComparatorを実装(ラムダ式が使えなかった時代の書き方)
        List<String> names = new ArrayList<>(List.of("ぱんまる", "たろう", "はなこ", "あいこ"));

        // 無名クラスを使った書き方
        names.sort(new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return a.length() - b.length();
            }
        });
        System.out.println("無名クラスでソート: " + names);

        // 上と同じことをラムダ式で書くと
        names.sort((a, b) -> b.length() - a.length());
        System.out.println("ラムダ式でソート: " + names);

        // 無名クラスでRunnableを実装
        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println("無名クラスのRunnable: 実行中");
            }
        };
        new Thread(task).start();

        // 同じことをラムダ式で
        new Thread(() -> System.out.println("ラムダ式のRunnable: 実行中")).start();
    }
}

コンパイル

javac AnonymousTest.java

実行

java AnonymousTest

実行結果は次のようになります。

Builderパターン: Person{name='ぱんまる', age=25, email='panmaru@example.com'}
無名クラスでソート: [たろう, はなこ, あいこ, ぱんまる]
ラムダ式でソート: [ぱんまる, たろう, はなこ, あいこ]
無名クラスのRunnable: 実行中
ラムダ式のRunnable: 実行中

コードの解説

メンバ内部クラス(Inner)は外側クラス(Outer)のインスタンスと紐づいているため、外側クラスの private フィールドやメソッドに直接アクセスできます。インスタンスを作るには outer.new Inner() のように外側クラスのインスタンスが必要です。

static ネストクラス(StaticNested)は外側クラスのインスタンスとは独立しているため、new Outer.StaticNested() のように外側クラスのインスタンスなしで使えます。外側クラスのインスタンスフィールドにはアクセスできませんが、static フィールドやメソッドにはアクセスできます。

static ネストクラスのよくある使い方が「Builderパターン」です。Person.Builder のようにクラス名から意図が明確に伝わり、name("ぱんまる").age(25).email("...").build() のようにメソッドチェーンで読みやすくオブジェクトを組み立てられます。フィールドが多いクラスのインスタンス生成に特に向いています。

無名クラスはインターフェースや抽象クラスをその場で実装する使い捨てのクラスです。Java 8以前はラムダ式がなかったため ComparatorRunnable の実装によく使われていました。現在はラムダ式の方が簡潔に書けますが、複数のメソッドを持つインターフェースを実装する場合は無名クラスが必要になります。

まとめ

内部クラスには4種類あり、特によく使われるのはメンバ内部クラス・staticネストクラス・無名クラスです。メンバ内部クラスは外側クラスのメンバに直接アクセスでき、staticネストクラスは外側クラスのインスタンスなしで使えるため Builderパターンなどに活用されます。無名クラスはインターフェースをその場で実装したい場面で使いますが、関数型インターフェースの場合はラムダ式の方が簡潔に書けます。内部クラスの仕組みを知ることで、これまで使ってきたIteratorの実装やデザインパターンへの理解も一層深まります。

2026/07/28(火) ぱんまる

コメント

タイトルとURLをコピーしました