内部クラス:クラスの中のクラス

docs

皆さん、以前の記事でJavaのクラスとオブジェクトの基本を理解できましたか?まだの人は、まずはこちらをチェックしてみてくださいね!

クラスとオブジェクトの概念 – オブジェクト指向の入り口 | ToolDocs

今回は、Javaのちょっと面白い仕組み「内部クラス」について掘り下げていきます。「クラスの中にクラスってどういうこと?」って思うかもしれませんね。でも大丈夫!具体例をたくさん使って、内部クラスの便利さを分かりやすく解説していきます。

内部クラスってなんだ?

内部クラスは、その名の通り別のクラスの内部で定義されるクラスのことです。外部のクラスを「エンクロージングクラス」とか「外部クラス」って呼び、内部クラスを「インナークラス」って呼びます。

なんでこんなことするかっていうと、いくつかメリットがあるんです。

  • 関連性の高いクラスをまとめる: 特定のクラスでしか使わないような、すごく密接な関係にあるクラスを一緒に定義することで、コードの見通しを良くしたり、管理しやすくしたりできます。
  • カプセル化の強化: 内部クラスは外部クラスのメンバー(privateなものも含めて!)にアクセスできます。これは、外部から直接触られたくないデータを内部クラスで操作する、なんて場合に便利です。
  • コードの構造化: 大規模なアプリケーションで、特定の機能に特化した小さなクラスを複数作りたい場合に、コードを整理しやすくなります。

「なるほど、便利そう!」って思いましたか?じゃあ、具体的にどんな内部クラスがあるのか見ていきましょう!

内部クラスの種類と使い方

内部クラスにはいくつか種類があります。それぞれ特徴があるので、違いを理解して使い分けましょう。

1. メンバー内部クラス(Member Inner Class)

これが一番基本的な内部クラスです。外部クラスのメンバー変数やメソッドと同じように定義されます。

Java

class Outer { // 外部クラス
    private String outerMessage = "こんにちは、外部クラスです!";

    class Inner { // メンバー内部クラス
        void display() {
            System.out.println(outerMessage); // 外部クラスのprivateメンバーにもアクセスできる!
        }
    }

    void createInner() {
        Inner inner = new Inner(); // 外部クラスの中から内部クラスのインスタンスを生成
        inner.display();
    }

    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.createInner(); // 外部クラスのメソッドを介して内部クラスの処理を実行

        // もしくは、外部から直接内部クラスのインスタンスを生成することも可能
        // Outer.Inner directInner = outer.new Inner();
        // directInner.display();
    }
}

この例では、Outerクラスの中にInnerクラスがあります。Innerクラスのdisplay()メソッドは、Outerクラスのprivateな変数outerMessageにアクセスできていますよね。これがメンバー内部クラスの大きな特徴です。

インスタンスの生成方法に注目してください。

  • 外部クラスの中から生成する場合は、new Inner();のように普通にインスタンスを生成できます。
  • 外部から直接生成する場合は、Outer.Inner directInner = outer.new Inner();のように、外部クラスのインスタンスを通じて生成する必要があります。

2. ローカル内部クラス(Local Inner Class)

メソッドの中で定義される内部クラスです。ちょっと特殊ですが、特定のメソッド内だけでしか使わないようなクラスを定義したい場合に便利です。

Java

class Calculator {
    void calculate(int a, int b) {
        // ローカル内部クラスはメソッドの中で定義される
        class Adder {
            int add() {
                return a + b; // メソッドの引数(実質final)にもアクセスできる
            }
        }
        Adder adder = new Adder();
        System.out.println("計算結果: " + adder.add());
    }

    public static void main(String[] args) {
        Calculator calc = new Calculator();
        calc.calculate(10, 5);
    }
}

Calculatorクラスのcalculateメソッドの中にAdderクラスが定義されています。Adderクラスはcalculateメソッドのスコープ内でしか使えません。また、ローカル内部クラスは、そのクラスが定義されているメソッドのfinalまたは実質finalな変数にアクセスできます。

paizaで実行した結果

3. 匿名内部クラス(Anonymous Inner Class)

名前の通り、名前を持たない内部クラスです。これは、インターフェースを実装したり、抽象クラスを継承したりする際に、一度しか使わないような簡易的なクラスを定義するのに非常によく使われます。

例えば、JavaのGUIプログラミングなんかでは、イベントリスナー(ボタンがクリックされたときに動く処理など)を定義する際によく利用されます。

まずはインターフェースを定義しましょう。

Java

interface Greeting {
    void sayHello();
}

そして、このGreetingインターフェースを匿名内部クラスで実装してみます。

Java

class Greeter {
    void greet() {
        // 匿名内部クラスの定義
        Greeting greeting = new Greeting() {
            @Override
            public void sayHello() {
                System.out.println("ハロー、匿名さん!");
            }
        }; // ここにセミコロンを忘れずに!

        greeting.sayHello();
    }

    public static void main(String[] args) {
        Greeter greeter = new Greeter();
        greeter.greet();
    }
}

new Greeting() { ... }; の部分が匿名内部クラスです。Greetingインターフェースを実装したクラスを、その場で「名前なし」で作成し、そのインスタンスをgreeting変数に代入しています。この形式を一度見たら、「あ、匿名内部クラスだ!」って分かるようになりますよ。

vscodeで実行した結果

4. 静的内部クラス(Static Inner Class)

これもメンバー内部クラスの一種ですが、staticキーワードが付きます。静的内部クラスは、外部クラスのインスタンスがなくてもインスタンスを生成できます

Java

class Library {
    private static String libraryName = "Java図書館";
    private String bookTitle = "デザインパターン入門";

    static class BookInfo { // 静的内部クラス
        void displayLibraryName() {
            System.out.println("図書館名: " + libraryName); // 外部クラスのstaticメンバーにアクセスできる
            // System.out.println(bookTitle); // エラー!外部クラスの非staticメンバーにはアクセスできない
        }
    }

    public static void main(String[] args) {
        // 外部クラスのインスタンスがなくてもインスタンスを生成できる
        Library.BookInfo bookInfo = new Library.BookInfo();
        bookInfo.displayLibraryName();

        // 外部クラスのインスタンスを生成してもいい
        // Library library = new Library();
        // Library.BookInfo bookInfo2 = new Library.BookInfo();
        // bookInfo2.displayLibraryName();
    }
}

静的内部クラスのポイントは、以下の2つです。

  • 外部クラスのインスタンスなしで生成可能: new Library.BookInfo(); のように、OuterClass.InnerClassの形式でインスタンスを生成できます。
  • 外部クラスのstaticメンバーにのみアクセス可能: 外部クラスの非staticなメンバー変数(上記の例のbookTitleなど)にはアクセスできません。

「どんな時に使うの?」って思うかもしれませんね。例えば、外部クラスに関連するけれど、外部クラスの特定のインスタンスに依存しないユーティリティ的なクラスを作りたい場合などに便利です。

vscodeで実行した結果

内部クラスを使う際の注意点

内部クラスは便利ですが、いくつか注意点があります。

  • 可読性: 内部クラスを多用しすぎると、コードが複雑になり、読みにくくなることがあります。本当に内部クラスである必要があるのか、よく検討しましょう。
  • メモリ使用量: 特に非静的内部クラスは、外部クラスのインスタンスへの参照を暗黙的に持つため、不用意に多くのインスタンスを生成するとメモリを消費する可能性があります。

まとめ


今回は「内部クラス」について学びました。

  • 内部クラスは、別のクラスの内部で定義されるクラス。
  • メンバー内部クラス:外部クラスのメンバーのように振る舞い、外部クラスの全メンバーにアクセス可能。
  • ローカル内部クラス:メソッド内で定義され、そのメソッド内でのみ利用可能。
  • 匿名内部クラス:名前を持たず、インターフェースの実装や抽象クラスの継承をその場で行う。
  • 静的内部クラスstatic修飾子を持ち、外部クラスのインスタンスなしで生成可能。外部クラスのstaticメンバーにのみアクセス可能。

内部クラスは、コードの構造化やカプセル化の強化に役立つ強力な機能です。それぞれの特徴を理解して、適切な場面で活用できるよう練習してみてください。

コメント

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