1. Javaにおける多重継承の概念と代替手段
    1. Javaにおける多重継承の現状と「ダイヤモンド問題」
    2. 多重継承の代替手段としてのインターフェースとその進化
    3. デフォルトメソッドがもたらす「安全な多重継承」の側面
  2. Javaの配列:一次元から多次元、そしてタプル的活用
    1. 基本的な配列の宣言と操作:一次元配列
    2. 複雑なデータ構造:多次元配列の活用法
    3. Javaにおけるタプル風のデータ表現とその限界
  3. Javaの多態性、抽象クラス・メソッドを理解する
    1. 多態性(ポリモーフィズム)の基本概念とメリット
    2. メソッドオーバーライドとオーバーロード:多態性の二つの顔
    3. 抽象クラスと抽象メソッドによる設計の柔軟性
  4. 例外処理の深掘り:チェック例外と非チェック例外、値渡し・参照渡し
    1. Java例外処理の基礎:try-catch-finallyブロック
    2. チェック例外と非チェック例外の使い分けと設計思想
    3. メソッド引数における値渡しと参照渡し(Javaの特殊性)
  5. Javaの定数、テキストブロック、そしてクラスのネスト構造
    1. 不変性を保証する定数:finalキーワードの活用
    2. Java 15で標準化されたテキストブロックで可読性向上
    3. クラス内にクラスを定義:ネストされたクラスの構造と種類
  6. まとめ
  7. よくある質問
    1. Q: Javaでは多重継承は可能ですか?
    2. Q: Javaで多次元配列とは具体的にどのようなものですか?
    3. Q: Javaの多態性とは何ですか?
    4. Q: Javaにおける値渡しと参照渡しは何が違いますか?
    5. Q: Javaの内部クラスとネストクラスの違いは何ですか?

Javaにおける多重継承の概念と代替手段

Javaを学ぶ上で避けては通れないのが、継承の概念です。特に、多重継承に関しては、他の言語とJavaのアプローチの違いを理解することが重要になります。Javaでは、特定の設計上の課題を避けるために、クラスの多重継承を直接サポートしていません。しかし、その代替手段として強力な機能を提供しており、これらを使いこなすことが、堅牢で柔軟なJavaアプリケーション開発の鍵となります。

Javaにおける多重継承の現状と「ダイヤモンド問題」

Javaでは、クラスが複数のスーパークラスから直接継承する「多重継承」をサポートしていません。この設計決定の背景には、「ダイヤモンド問題」と呼ばれる複雑な問題があります。ダイヤモンド問題とは、あるクラスCが、共通の親クラスAを持つ複数のクラスB1とB2から継承した場合に発生します。もしクラスAに定義されたメソッドがB1とB2でそれぞれオーバーライドされていた場合、Cがそのメソッドを呼び出した際に、B1の実装とB2の実装のどちらを呼び出すべきか、コンパイラが判断できなくなるというものです。

この曖昧さを避けるために、Javaではクラスの階層において単一継承のみを許可しています。これにより、コンパイル時の混乱や予期せぬ実行時エラーを防ぎ、コードの予測可能性と安全性を高めています。オブジェクト指向設計において、この決定はJavaの堅牢性を支える重要な要素となっています。他の言語では多重継承を異なるアプローチで解決していますが、Javaはこの問題を根本的に回避する道を選びました。

この制約は、一見すると不便に感じるかもしれませんが、よりクリーンで明確な設計原則を促す結果にも繋がっています。開発者は、複雑な継承関係に頼るのではなく、より柔軟な設計パターンやJavaの他の強力な機能を利用するよう促されます。(参考情報より)

多重継承の代替手段としてのインターフェースとその進化

クラスの多重継承ができないJavaにおいて、複数の型を実装するために利用されるのが「インターフェース」です。インターフェースは、メソッドのシグネチャ(戻り値の型、メソッド名、引数の型と数)のみを定義し、具体的な実装は各クラスに委ねるという特徴を持っています。これにより、Javaは「型としての多重継承」を実現し、コードの再利用性と型安全性の両立を可能にしています。

例えば、Runnableインターフェースを実装するクラスは、それがどのようなクラスであってもrun()メソッドを持つことが保証されます。これにより、様々なクラスのオブジェクトをRunnable型として一貫して扱うことができるようになります。この仕組みは、デザインパターンの「ストラテジーパターン」や「デコレーターパターン」など、柔軟な設計を構築する上で不可欠です。

さらに、Javaのインターフェースは時代とともに進化してきました。特にJava 8からは、インターフェースにデフォルトメソッド(実装を持つメソッド)を定義できるようになりました。これは、既存のインターフェースに新しいメソッドを追加する際に、そのインターフェースを実装しているすべてのクラスを修正する必要がなくなるという画期的な変更でした。これにより、ライブラリの互換性が大きく向上し、インターフェースの利用範囲がさらに広がりました。(参考情報より)

デフォルトメソッドがもたらす「安全な多重継承」の側面

Java 8で導入されたデフォルトメソッドは、インターフェースに具体的なメソッド実装を持たせることができる機能です。これにより、インターフェースを実装する既存のクラスがその新しいメソッドの実装を強制されることなく、インターフェースに機能を追加できるようになりました。これは、Javaがクラスの多重継承を避けてきた歴史の中で、ある種の「安全な多重継承」の側面をもたらしたと言えます。

デフォルトメソッドの最大の利点は、下位互換性を保ちながらインターフェースを進化させられる点にあります。例えば、数多くのアプリケーションで利用されているインターフェースに新機能を追加したい場合、デフォルトメソッドを使えば、既存のアプリケーションがコンパイルエラーになることなく、新しい機能を利用できるようになります。実装するクラスがデフォルトメソッドをオーバーライドしない限り、インターフェースで定義されたデフォルト実装が使用されます。

ただし、複数のインターフェースが同じシグネチャを持つデフォルトメソッドを定義している場合、それを実装するクラスはどちらのデフォルトメソッドを使用すべきか曖昧になるため、明示的にオーバーライドして解決する必要があります。この点は、まさにダイヤモンド問題の類似ケースであり、Javaが多重継承の問題にどう向き合ってきたかを示す良い例と言えるでしょう。(参考情報より)

Javaの配列:一次元から多次元、そしてタプル的活用

Javaプログラミングにおいて、データを効率的に管理するための基本的な構造の一つが「配列」です。配列は、同じ型の複数の値をまとめて格納できるデータ構造であり、一次元から多次元まで、様々な形で利用されます。また、Javaには直接的な「タプル」型は存在しませんが、その概念を模倣して、複数の異なる型のデータを一時的にグループ化する手法も存在します。これらの知識は、Javaでデータ構造を扱う上で非常に重要です。

基本的な配列の宣言と操作:一次元配列

一次元配列は、最も基本的な配列の形であり、一連の要素を直線的に並べたものです。Javaで一次元配列を宣言する際は、まず要素の型と配列名、そして角括弧 [] を指定します。

int[] numbers; // int型の配列を宣言
numbers = new int[5]; // 5つの要素を持つ配列を初期化(デフォルト値は0)

// または、宣言と同時に初期化
String[] names = {"Alice", "Bob", "Charlie"};

// 要素へのアクセスはインデックス(0から始まる)を使用
System.out.println(names[0]); // "Alice"が出力される
numbers[0] = 10; // 最初の要素に値を代入

// 配列の長さは .length プロパティで取得
System.out.println(names.length); // 3が出力される

配列のサイズは、一度初期化されると変更できません。このため、配列のサイズが事前に不明な場合や、頻繁に要素の追加・削除が必要な場合は、ArrayListなどの動的配列(コレクションフレームワーク)を使用することが推奨されます。配列は、要素が固定長で、インデックスによる高速なアクセスが求められる場合に特に有効です。(Javaの基本機能より)

複雑なデータ構造:多次元配列の活用法

Javaの多次元配列は、「配列の配列」として実装されます。最も一般的なのは二次元配列で、これは表形式のデータや行列を表現するのに適しています。三次元以上の配列も可能ですが、コードの可読性を考慮すると、通常は二次元配列までがよく利用されます。

// 二次元配列の宣言と初期化
int[][] matrix = new int[3][4]; // 3行4列の配列

// 初期化と同時に値を代入
String[][] schedule = {
    {"月曜", "Java", "SQL"},
    {"火曜", "Python", "Web"}
};

// 要素へのアクセス
System.out.println(schedule[0][1]); // "Java"が出力される
matrix[0][0] = 1;

// 各行の長さが異なる「ジャグ配列」も可能
int[][] irregularArray = new int[3][];
irregularArray[0] = new int[2]; // 1行目は2列
irregularArray[1] = new int[3]; // 2行目は3列
irregularArray[2] = new int[1]; // 3行目は1列

多次元配列は、画像処理におけるピクセルデータや、ゲーム盤の状態管理、スプレッドシートのようなデータの格納に非常に役立ちます。また、内側の配列がそれぞれ異なる長さを持ちうる「ジャグ配列(不規則配列)」の概念も理解しておくと、より柔軟なデータ構造を構築できます。多次元配列を効果的に使うことで、複雑な情報を整理し、効率的にプログラムで扱えるようになります。(Javaの基本機能より)

Javaにおけるタプル風のデータ表現とその限界

PythonやC#のような言語には、複数の異なる型のデータをまとめて一時的に保持するための「タプル(Tuple)」型が組み込まれています。しかし、Javaには標準でタプル型は提供されていません。それでも、複数の値をまとめて返したい場合や、一時的にグループ化したい場合に、タプルに似た機能を実現する方法がいくつか存在します。

  • カスタムクラス(POJO / Record): 最も堅牢な方法は、必要なフィールドを持つ専用のクラスを作成することです。Java 16で導入されたRecord型は、データ保持に特化した簡潔なクラスを定義でき、タプルに近い使い勝手を提供します。
  • record Point(int x, int y) {}
    Point p = new Point(10, 20);
    System.out.println(p.x() + ", " + p.y());
  • Map.EntryまたはPairライブラリ: Apache Commons Langなどの外部ライブラリには、Pairクラスが提供されており、2つの要素を保持できます。また、Java標準のAbstractMap.SimpleEntryも2つの要素のペアとして利用可能です。
  • Object[]: 最も単純ですが、型安全性が低い方法として、Object型の配列を使用することもあります。しかし、要素を取り出す際に型キャストが必要となり、実行時エラーのリスクが高まります。

Record型の登場により、Javaでのタプル風のデータ表現は格段に扱いやすくなりました。しかし、タプル本来の「一時的で匿名な型」という特性とは異なり、Recordは名前付きのクラスであるため、その利用シーンを適切に判断することが重要です。(Java 16の新機能、およびJavaの一般的なコーディングプラクティスより)

Javaの多態性、抽象クラス・メソッドを理解する

Javaのオブジェクト指向プログラミング(OOP)の三大要素の一つが「多態性(ポリモーフィズム)」です。多態性は、同じインターフェースやスーパークラスを通じて、異なる型のオブジェクトを統一的に扱うことを可能にし、プログラムの柔軟性、拡張性、保守性を劇的に向上させます。この強力な概念を理解するには、メソッドのオーバーライドとオーバーロード、そして抽象クラスや抽象メソッドの役割が不可欠です。

多態性(ポリモーフィズム)の基本概念とメリット

多態性(Polymorphism)とは、「多くの形を取れる」という意味を持ち、オブジェクト指向プログラミングにおいて、一つのインターフェースが複数の異なるクラスによって実装されたり、一つのメソッドがサブクラスによって異なる振る舞いをしたりすることを指します。これにより、開発者は特定のオブジェクトの具体的な型を知らなくても、共通のインターフェースを通じて操作を行うことができます。

多態性の主なメリットは以下の通りです。

  • コードの柔軟性と拡張性: 新しいクラスを追加しても、既存のコード(共通インターフェースを使用している部分)を変更することなく、その新しいクラスを処理に追加できます。
  • コードの保守性: コードの重複を減らし、より簡潔で理解しやすいコードになります。特定の振る舞いを変更する場合も、関連するサブクラスのみを修正すればよい場合が多いです。
  • 可読性の向上: 共通の型を使って処理を記述できるため、抽象度が高まり、コードの意図が明確になります。

例えば、Animalというスーパークラスがあり、DogCatというサブクラスがあるとします。Animal animal = new Dog();のように宣言することで、animalという変数を通してDogオブジェクトを、あたかもAnimalとして扱うことができます。これは多態性の典型的な例です。(参考情報より)

メソッドオーバーライドとオーバーロード:多態性の二つの顔

Javaにおける多態性は、主に「メソッドオーバーライド」と「メソッドオーバーロード」という2つのメカニズムによって実現されます。これらは名前が似ていますが、全く異なる概念です。

  • メソッドオーバーライド(Override): スーパークラスで定義されたメソッドを、サブクラスで再定義することです。これにより、同じメソッド名でも、呼び出し元のオブジェクトの型に応じて異なる処理を実行させることができます。実行時にどのメソッドが呼び出されるかは、オブジェクトの実際の型によって決定されます(動的ディスパッチ)。
  • class Animal { void speak() { System.out.println("鳴く"); } }
    class Dog extends Animal { @Override void speak() { System.out.println("ワン"); } }
    // Animal a = new Dog(); a.speak(); // "ワン"が出力
  • メソッドオーバーロード(Overload): 同じクラス内で、同じメソッド名でも引数の型や数が異なるメソッドを複数定義することです。コンパイラは、引数の情報に基づいて適切なメソッドを選択します(静的ディスパッチ)。
  • class Calculator {
        int add(int a, int b) { return a + b; }
        double add(double a, double b) { return a + b; }
    }

オーバーライドは継承関係にあるクラス間で、実行時の振る舞いを多様化するのに使われます。一方、オーバーロードは同じクラス内で、異なる引数のパターンに対応するために使われ、メソッド名の再利用を可能にします。両者ともにJavaの多態性を形作る重要な要素です。(参考情報より)

抽象クラスと抽象メソッドによる設計の柔軟性

Javaの多態性をさらに強化するのが「抽象クラス」と「抽象メソッド」です。これらは、共通の振る舞いを強制しつつ、具体的な実装はサブクラスに委ねることで、設計の柔軟性と堅牢性を両立させます。

  • 抽象メソッド: 実装を持たず、メソッドのシグネチャ(名前、戻り値、引数)のみが定義されたメソッドです。メソッドの宣言の前にabstractキーワードを付けます。
  • 抽象クラス: 少なくとも1つ以上の抽象メソッドを持つクラスです。クラスの宣言の前にabstractキーワードを付けます。抽象クラスは、それ自身をインスタンス化することはできません。必ずサブクラスで抽象メソッドを実装(オーバーライド)する必要があります。
abstract class Shape { // 抽象クラス
    abstract double getArea(); // 抽象メソッド
    void display() { System.out.println("図形です"); } // 通常のメソッドも持てる
}

class Circle extends Shape {
    double radius;
    Circle(double radius) { this.radius = radius; }
    @Override
    double getArea() { return Math.PI * radius * radius; }
}

抽象クラスは、共通の基本構造や契約を定義し、サブクラスにその契約の実装を強制することで、設計の一貫性を保ちます。インターフェースとの違いは、抽象クラスはフィールドやコンストラクタ、通常のメソッドも持てる点です。これにより、共通の機能を提供しつつ、特定の処理だけサブクラスに任せたい場合に非常に有効です。複雑なシステム設計において、抽象クラスと抽象メソッドは、拡張性とメンテナンス性を高める上で重要な役割を果たします。(Javaの基本機能より)

例外処理の深掘り:チェック例外と非チェック例外、値渡し・参照渡し

Javaアプリケーションを開発する上で、予期せぬエラーや異常な状況に適切に対応することは、プログラムの安定性と信頼性を保つために不可欠です。このためにJavaが提供しているのが「例外処理」のメカニズムです。例外には大きく分けて二つの種類があり、それぞれ異なる対処が求められます。また、メソッド呼び出しにおける引数の「値渡し」と「参照渡し」の概念も、プログラムの動作を理解する上で非常に重要です。

Java例外処理の基礎:try-catch-finallyブロック

Javaの例外処理は、主にtry-catch-finallyブロックを用いて行われます。これは、リスクのあるコードを実行し、万一例外が発生した場合にそれを捕捉し、適切に対処するための構造です。

  • tryブロック: 例外が発生する可能性のあるコードを記述します。
  • catchブロック: tryブロック内で特定の例外が発生した場合に、その例外を捕捉し、対応する処理を記述します。複数のcatchブロックを連鎖させることで、異なる種類の例外に対応できます。
  • finallyブロック: tryブロックの処理が正常終了したか、例外が発生したかに関わらず、必ず実行されるコードを記述します。主にリソースの解放(ファイルのクローズ、データベース接続の切断など)に利用されます。
try {
    // 例外が発生する可能性のある処理
    int result = 10 / 0; // ArithmeticExceptionが発生
    System.out.println("結果: " + result);
} catch (ArithmeticException e) {
    // ArithmeticExceptionが発生した場合の処理
    System.err.println("ゼロ除算エラーが発生しました: " + e.getMessage());
} catch (Exception e) {
    // その他の例外を捕捉(より一般的な例外は最後に)
    System.err.println("予期せぬエラーが発生しました: " + e.getMessage());
} finally {
    // 例外の有無にかかわらず必ず実行される処理
    System.out.println("例外処理ブロックを終了します。");
}

Java 7からは、複数の例外を1つのcatchブロックで処理できる「マルチキャッチ」や、リソースが自動的にクローズされる「try-with-resources」文も導入され、例外処理の記述がより簡潔かつ安全になりました。これらの機能は、コードの可読性を高め、リソースリークを防ぐのに役立ちます。(Javaの基本機能およびJava 7の新機能より)

チェック例外と非チェック例外の使い分けと設計思想

Javaの例外は、大きく「チェック例外(Checked Exception)」と「非チェック例外(Unchecked Exception)」の二種類に分類されます。この分類は、Javaの例外処理設計における重要な思想を反映しています。

  1. チェック例外 (Checked Exception):
    • Exceptionクラスを継承し、RuntimeExceptionを継承しない例外。
    • コンパイル時に検出され、必ずtry-catchで捕捉するか、throwsキーワードで呼び出し元に伝播させる必要がある。
    • 例:IOException, SQLException, ClassNotFoundException
    • 設計思想: 回復可能で、プログラマーが対処すべきと期待される状況。外部システムとの連携(ファイルI/O、ネットワーク通信、データベースアクセス)などで発生し、プログラムが正常に戻れる可能性のあるエラー。
  2. 非チェック例外 (Unchecked Exception):
    • RuntimeExceptionクラスを継承する例外。
    • コンパイル時にはチェックされず、明示的なtry-catchthrowsは必須ではない(記述は可能)。
    • 例:NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException
    • 設計思想: プログラマーのバグや論理的な誤りに起因する、通常は回復不可能な状況。事前のチェックや修正で回避できるべきエラー。

非チェック例外を積極的に捕捉するのは、通常は推奨されません。それらはコードの欠陥を示すため、修正されるべきバグとして扱われます。一方、チェック例外は、起こりうるが制御不能な状況への対応として、設計段階から考慮し、適切な回復処理を実装する必要があります。(Javaの例外処理設計ガイドラインより)

メソッド引数における値渡しと参照渡し(Javaの特殊性)

プログラミング言語におけるメソッド引数の渡し方には、「値渡し(Pass by Value)」と「参照渡し(Pass by Reference)」の二種類があります。Javaは「全て値渡しである」と説明されることが多いですが、オブジェクトを扱う際には少し複雑な理解が必要です。厳密には「参照の値渡し」と表現するのが正確です。

  • プリミティブ型(int, boolean, charなど):
    メソッドにプリミティブ型の値を渡すと、その値のコピーが渡されます。メソッド内で引数の値を変更しても、呼び出し元の変数の値は変わりません。これは純粋な値渡しです。
  • void increment(int x) { x++; }
    int num = 10;
    increment(num); // numは10のまま
  • オブジェクト型(インスタンス):
    メソッドにオブジェクトを渡す場合、オブジェクトそのものが渡されるわけではなく、そのオブジェクトへの「参照(メモリ上のアドレス)」のコピーが渡されます。つまり、呼び出し元の変数とメソッド内の引数は、同じオブジェクトを指すことになります。

    • メソッド内で参照が指すオブジェクトの状態を変更すると、呼び出し元にも影響します。
    • しかし、メソッド内で引数の参照を別のオブジェクトに再代入しても、呼び出し元の参照は変わりません
    class MyObject { int value; }
    void change(MyObject obj, int val) {
        obj.value = val; // オブジェクトの状態が変更される
        obj = new MyObject(); // 参照を別のオブジェクトに再代入 (呼び出し元には影響しない)
    }
    MyObject original = new MyObject();
    original.value = 10;
    change(original, 20);
    System.out.println(original.value); // 20が出力される
    

この「参照の値渡し」の特性を理解することは、特にオブジェクトの状態を変更するメソッドを設計する際に非常に重要です。Javaでは、メソッド内で新しいオブジェクトを生成して引数に再代入しても、呼び出し元の変数が指すオブジェクトは変化しないという点を覚えておきましょう。(Javaのメモリ管理とメソッド呼び出し規約より)

Javaの定数、テキストブロック、そしてクラスのネスト構造

Javaプログラミングでは、コードの可読性、保守性、そして安全性を高めるための様々な機能が提供されています。その中でも、値の不変性を保証する「定数」、複数行文字列を扱うための「テキストブロック」、そして密接に関連するクラスを組織的に配置する「ネストされたクラス」の概念は、コード品質を向上させる上で非常に役立ちます。これらの機能を適切に利用することで、より洗練されたJavaアプリケーションを構築できます。

不変性を保証する定数:finalキーワードの活用

Javaにおける「定数」とは、一度値が代入されたら二度と変更できない変数のことです。定数を定義するには、finalキーワードを使用します。定数を使用することで、プログラムの意図を明確にし、意図しない値の変更を防ぎ、コードの信頼性を高めることができます。

finalキーワードは、変数だけでなく、メソッドやクラスにも適用でき、それぞれ異なる意味を持ちます。

  • final変数:
    • ローカル変数: メソッド内で定義され、一度だけ値を代入可能。
    • インスタンス変数(フィールド): 各オブジェクトごとに値を持ち、コンストラクタまたは宣言時に一度だけ値を代入可能。
    • クラス変数(static final): クラス全体で共有される定数。宣言時に初期化する必要があり、通常はすべて大文字で記述する(例: public static final int MAX_VALUE = 100;)。
  • finalメソッド: サブクラスでのオーバーライドを禁止します。基底クラスの特定の振る舞いを変更させたくない場合に利用されます。
  • finalクラス: 継承を禁止します。そのクラスが完全な実装であり、拡張されるべきではない場合に利用されます(例: Stringクラス)。

特にpublic static finalで定義されるクラス定数は、プログラム全体で共有される不変の値(例: 円周率Math.PI)や設定値によく使われます。これにより、マジックナンバー(意味不明な数値リテラル)を排除し、コードの可読性と保守性を向上させることができます。(Javaの基本機能より)

Java 15で標準化されたテキストブロックで可読性向上

Java 15で標準機能として導入された「テキストブロック」は、複数行にわたる文字列を扱う際の可読性と記述性を劇的に向上させる機能です。それまでは、複数行の文字列(SQLクエリ、HTML、JSONなど)をJavaの文字列リテラルで表現するには、改行コード\nや文字列結合演算子+を多用する必要があり、非常に読みにくく、エスケープシーケンスの管理も煩雑でした。

テキストブロックは、3つのダブルクォーテーション(""")で開始し、同じく3つのダブルクォーテーションで終了します。このブロック内で記述された文字列は、エスケープシーケンスを使わずにそのままの形で表現できます。インデントも自動的に処理され、読みやすい形で記述できます。

// テキストブロックがない場合
String jsonLegacy = "{\n" +
                    "  \"name\": \"Alice\",\n" +
                    "  \"age\": 30\n" +
                    "}";

// テキストブロックを使用した場合 (Java 15以降)
String jsonTextBlock = """
{
  "name": "Bob",
  "age": 25
}
""";

System.out.println(jsonLegacy);
System.out.println(jsonTextBlock);

テキストブロックは、特に外部ファイルの内容を文字列として扱う場合や、テンプレートエンジンを使わずに簡単なHTMLやSQLをJavaコード内に直接埋め込む場合に非常に有効です。エスケープ文字の管理が不要になることで、エラーの発生を抑え、コードレビューを容易にし、開発効率を向上させます。これはJavaが言語としての表現力を高める上で重要な進化と言えるでしょう。(Java 15公式ドキュメントより)

クラス内にクラスを定義:ネストされたクラスの構造と種類

Javaでは、一つのクラスの内部に別のクラスを定義することができます。これを「ネストされたクラス(Nested Classes)」と呼びます。ネストされたクラスは、それを囲むクラス(エンクロージングクラス)との密接な関連性や特定の目的に特化したクラスを、論理的にグループ化するために利用されます。ネストされたクラスは、大きく「静的ネストクラス(Static Nested Classes)」と「内部クラス(Inner Classes)」に分類され、内部クラスはさらにいくつかの種類に分かれます。

  1. 静的ネストクラス (Static Nested Classes):
    • staticキーワードで修飾されます。
    • エンクロージングクラスのインスタンスがなくてもインスタンス化できます。
    • エンクロージングクラスのstaticメンバーにはアクセスできますが、非staticメンバーには直接アクセスできません。
    • 主に、エンクロージングクラスと論理的に関連しているが、そのインスタンスに依存しないユーティリティクラスなどに使用されます。
  2. 内部クラス (Inner Classes):
    • staticキーワードを持ちません。
    • エンクロージングクラスのインスタンスに関連付けられます。エンクロージングクラスのインスタンスがないとインスタンス化できません。
    • エンクロージングクラスのすべてのメンバー(static、非staticprivateを含む)に直接アクセスできます。
    • メンバー内部クラス: エンクロージングクラスのフィールドと同じレベルで定義されます。
    • ローカルクラス: メソッドの内部で定義され、そのメソッド内でのみ有効です。
    • 匿名クラス: 名前のないクラスで、通常はインターフェースや抽象クラスを実装する際に、その場で一度だけ使用される実装を提供します。

ネストされたクラスは、カプセル化を強化し、コードを論理的に組織化し、特定のクラスが他のクラスに深く依存するような状況で非常に役立ちます。例えば、GUIイベントリスナーやイテレーターの実装などで匿名クラスが頻繁に利用されます。(Javaの基本機能より)