Menu
Coddy logo textTech

内部・ネスト・匿名クラス

CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 49/87。

Javaでは、クラスの中に別のクラスを定義することができます。これらの内部クラスは、関連する機能をグループ化することでコードを整理するのに役立ち、外部クラスのメンバに直接アクセスすることができます。

ネストされた内部クラスは、別のクラスのメンバーとして定義されます。これは、プライベートなものも含め、外部クラスのすべてのフィールドとメソッドにアクセスできます:

class Outer {
    private String message = "Hello";
    
    class Inner {
        void display() {
            System.out.println(message);  // privateフィールドにアクセス可能
        }
    }
}

// 使い方
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.display();  // 出力: Hello

ローカル内部クラスは、メソッド内で定義されます。そのメソッド内でのみ使用でき、事実上final(effectively final)なローカル変数にアクセスできます:

void process() {
    String prefix = "Result: ";
    
    class LocalHelper {
        void print(int value) {
            System.out.println(prefix + value);
        }
    }
    
    new LocalHelper().print(42);
}

匿名内部クラスは、名前を持たないクラスであり、1つの式の中で作成とインスタンス化が同時に行われます。これは通常、インターフェースの実装や、その場でのクラスの拡張によく使用されます。

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

内部クラスは、あるクラスが他の1つのクラスにのみ関連している場合に特に有用であり、コードを整理し、カプセル化された状態に保つのに役立ちます。

challenge icon

チャレンジ

簡単

3つの異なる種類の内部クラス(インナークラス)を活用したショッピングカートシステムを構築しましょう。ShoppingCartクラスを作成し、アイテムを表すためのメンバ内部クラス、割引を計算するためのローカル内部クラス、そして最終的なレシートを整形するための匿名内部クラスを使用します。

コードは以下の2つのファイルに分けて構成します:

  • ShoppingCart.java: ショッピング体験を管理する外部クラスを作成します。ShoppingCartには、privateフィールドの customerName (String) を持たせてください。

    ShoppingCartの内部に、メンバ内部クラスとして Item を定義します。これには2つのprivateフィールド name (String) と price (double) を持たせます。Itemには両方のフィールドを初期化するコンストラクタと、[name]: $[price] という形式の文字列を返す getDetails() メソッドを作成してください。

    ShoppingCartには顧客名を受け取るコンストラクタと、Itemと割引率(double)を受け取る checkout() メソッドが必要です。checkout() メソッドの内部で以下を行います:

    1. ローカル内部クラス DiscountCalculator を作成し、割引率を適用した後の価格を返す applyDiscount(double originalPrice) メソッドを定義します。
    2. このローカルクラスを使用して、最終価格を計算します。
    3. レシートを整形するために、Formatter インターフェース(後述)を実装する 匿名内部クラス を作成します。この匿名クラスは、複数行のレシート文字列を返す format() メソッドをオーバーライドする必要があります。

    checkout() メソッドは、以下の正確な形式で整形されたレシート文字列を返す必要があります:

    --- Receipt ---
    Customer: [customerName]
    Item: [item details from getDetails()]
    Discount: [discount]%
    Final Price: $[calculated price]
  • Formatter.java: String format() という単一のメソッドを持つシンプルなインターフェースを定義します。このインターフェースは、checkoutメソッド内の匿名内部クラスによって実装されます。
  • Main.java: ショッピングカートを実際に動かしてみましょう!顧客名 (String)、アイテム名 (String)、アイテム価格 (double) の3つの入力を受け取ります。割引率は 10.0 に固定されています。

    顧客名を使用して ShoppingCart インスタンスを作成します。次に、外部クラスの参照構文 cart.new Item(name, price) を使用して Item インスタンスを作成します。アイテムと割引率を指定して checkout() を呼び出し、返されたレシートを出力してください。

入力は、顧客名、アイテム名、アイテム価格の順に3つ与えられます。

例えば、入力が AliceLaptop1000.0 の場合、10%割引後の最終価格は 900.0 になります。

それぞれの内部クラスが異なる目的を果たしていることに注目してください。メンバ内部クラスは関連するデータを外部クラスとグループ化し、ローカルクラスは特定のメソッド内でのみ必要なロジックをカプセル化し、匿名クラスはインターフェースの迅速な使い捨ての実装を提供します!

チートシート

Javaでは、クラスの中に別のクラスを定義することができます。これらの内部クラス(inner classes)は、関連する機能をグループ化することでコードを整理するのに役立ち、外部クラスのメンバに直接アクセスできます。

ネストした内部クラス

ネストした内部クラスは、別のクラスのメンバとして定義されます。これは、privateなものも含め、外部クラスのすべてのフィールドやメソッドにアクセスできます:

class Outer {
    private String message = "Hello";
    
    class Inner {
        void display() {
            System.out.println(message);  // privateフィールドにアクセス可能
        }
    }
}

// 使用例
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.display();  // 出力: Hello

ローカル内部クラス

ローカル内部クラスは、メソッドの内部で定義されます。そのメソッド内でのみ使用可能で、実質的にfinalなローカル変数にアクセスできます:

void process() {
    String prefix = "Result: ";
    
    class LocalHelper {
        void print(int value) {
            System.out.println(prefix + value);
        }
    }
    
    new LocalHelper().print(42);
}

匿名内部クラス

匿名内部クラスは、名前のないクラスであり、1つの式の中で作成とインスタンス化が行われます。インターフェースの実装やクラスの拡張をその場で行う際によく使用されます:

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

内部クラスは、あるクラスが他の1つのクラスにのみ関連している場合に特に有用であり、コードの整理とカプセル化を維持するのに役立ちます。

自分で試してみよう

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 入力を読み込む
        String customerName = scanner.nextLine();
        String itemName = scanner.nextLine();
        double itemPrice = scanner.nextDouble();
        
        double discount = 10.0;
        
        // TODO: 顧客名を使用して ShoppingCart を作成する
        
        // TODO: cart.new Item(itemName, itemPrice) を使用して Item を作成する
        
        // TODO: item と discount を指定して checkout() を呼び出し、結果を出力する
        
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン