1. 内部クラス(inner class)とは?
Java ではクラスはトップレベル(ファイル直下)だけでなく、別のクラスの内部にも宣言できます。このようなクラスはネストされた(nested)クラスと呼ばれます。こうしたクラスを static 修飾子なしで宣言した場合、それは内部(non-static inner、略して inner)クラスと呼ばれます。
内部クラスとは、別のクラスの内部に宣言され、その外部クラスのインスタンスと結び付いているクラスです。外部クラスのフィールドやメソッドが private であってもアクセスできます。たとえるなら、オブジェクトに何でも許される「秘密の助っ人」が付いているようなものです。
家と部屋の関係に似ています。家が外部クラス、部屋が内部クラスです。部屋は家なしには存在できませんが、家の資源(照明、暖房、家具)にアクセスできます。家が消えれば部屋も消えます。Java の内部クラスもまさに同じ仕組みです。
内部クラスが必要になる理由
- 論理的な結び付き: あるクラスが他のクラスと一緒に使われる前提で、単独では意味をなさないとき。
- カプセル化: 実装の詳細を隠し、パッケージの名前空間を汚さない。
- private メンバーへのアクセス: 内部クラスは外部クラスの private フィールドやメソッドにアクセスできる。
- コンパクトさ: パッケージ階層に「不要な」トップレベルクラスを増やさない。
内部クラスの宣言構文
宣言は簡単です。別のクラス本体の中で、static 修飾子を付けずに宣言します。
class Outer {
// 外部クラスのフィールドとメソッド
class Inner {
// 内部クラスのフィールドとメソッド
void printHello() {
System.out.println("Hello from Inner!");
}
}
}
ここで Inner は Outer に対する内部クラスです。
図: 可視化
Outer
│
├─ フィールド/メソッド
│
└─ Inner (内部クラス)
└─ 自身のフィールド/メソッド
2. 内部クラスのインスタンスを生成する方法
ここは初心者がよくハマる落とし穴のひとつです!内部クラスのインスタンスは、常に特定の外部クラスのオブジェクトに結び付いています。
例:
Outer outer = new Outer(); // 外部クラスのオブジェクトを生成
Outer.Inner inner = outer.new Inner(); // 外部オブジェクト経由で内部クラスを生成!
inner.printHello(); // Hello from Inner!
単に new Inner() と書くとコンパイルエラーになります。Java は、その内部クラスをどの外部クラスのオブジェクトに結び付けるべきか分からないからです。
なぜそうなるのか?
内部クラスは外部クラスのフィールドやメソッドにアクセスできます。だからこそ、どの外部クラスのオブジェクトと結び付いて動作するのかを「知っている」必要があります。
3. 内部クラスの利用例
内部クラスが実際に役立つ場面を見てみましょう。
例 1: 「バックパックとアイテム」のモデル
たとえば Backpack クラスがアイテム(Item)を保持できるとします。ただし Item クラスは Backpack の内部だけであれば十分で、バックパックの外では関心がないとします。
public class Backpack {
private String owner;
public Backpack(String owner) {
this.owner = owner;
}
// 内部クラス — アイテムはバックパックの中でのみ存在できる!
class Item {
private String name;
public Item(String name) {
this.name = name;
}
public void printInfo() {
// 魔法!外部クラスの private フィールドが見える
System.out.println(owner + " はアイテムを持っています: " + name);
}
}
}
使用例:
Backpack bp = new Backpack("Vasya");
Backpack.Item item = bp.new Item("Java の教科書");
item.printInfo(); // Vasya はアイテムを持っています: Java の教科書
注: Item は owner フィールドが private でもアクセスできます!
例 2: 自作コレクションのイテレータ
Java のコレクションでは、内部クラスでイテレータを実装することがよくあります。ここでは、内部クラスのイテレータを持つ簡単なコレクションを作ってみます。
public class IntList {
private int[] data = new int[10];
private int size = 0;
public void add(int value) {
data[size++] = value;
}
// 内部クラス — イテレータ!
class Iterator {
private int index = 0;
public boolean hasNext() {
return index < size;
}
public int next() {
return data[index++];
}
}
}
使用例:
IntList list = new IntList();
list.add(10);
list.add(20);
IntList.Iterator it = list.new Iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
4. 内部クラスの特徴
外部クラスのメンバーへのアクセス
内部クラスはあらゆる(private を含む)外部クラスのフィールドやメソッドにアクセスできます。便利ですが注意も必要です。外部クラスの実装が変わると、内部クラスが思わぬ形で「壊れる」可能性があります。
class Outer {
private int secret = 42;
class Inner {
void showSecret() {
System.out.println("秘密: " + secret);
}
}
}
内部クラスは static メンバーを持てない
内部(non-static inner)クラスは定数(たとえば public static final int MY_CONST = 123;)を除き、static フィールドやメソッドを持つことはできません。これは、内部クラスが常に外部クラスのインスタンスに「ひも付く」ためです。
static なネストクラスを宣言したい場合は、static 修飾子を使ってください(この点は次回の講義で扱います)。
内部クラスの可視性
内部クラスの可視性は private、protected、public、またはパッケージプライベート(package-private)にできます。
public class Outer {
private class Inner { /* ... */ }
}
5. 便利な注意点
内部クラスを使うべきとき
内部クラスは見た目のためではなく、コードを論理的に整理するためのものです。次のような場合に使いましょう:
- クラスが 1 箇所でしか使われない(例: 補助的なイテレータ、ハンドラ、複雑な構造の一部)。
- クラスが外部クラスと密接に関連し、外側では意味を持たない。
- パッケージ内の他のクラスから実装詳細を隠したい。
流行だからという理由だけで内部クラスを使わないでください! 他の場所でも役立つなら、トップレベルに切り出したほうが良い場合もあります。
内部クラスと this
内部クラスの中から、OuterClassName.this を使って外部クラスのフィールドやメソッドにアクセスできます。
class Car {
private String model = "Tesla";
class Engine {
void printModel() {
// 明示的に外側のオブジェクトを参照
System.out.println("モデル: " + Car.this.model);
}
}
}
この構文は、内部クラスと外部クラスで同名のフィールドがある場合に特に便利です。
内部クラスを使うべきでないとき
次のような場合は使わないでください:
- 内部クラスが外部クラスのフィールド/メソッドにアクセスしない
- そのクラスがプログラムの他の場所でも有用になり得る
- 外部クラスが大きく複雑になりすぎる
そのような場合は次のほうが良いでしょう:
- クラスを static なネストクラス(static class)にする
- クラスを別ファイルに分離する
- クラス化せず通常のメソッドで代替する
6. 内部クラスでよくあるミス
エラー 1: 外部クラスのオブジェクトなしに内部クラスを生成しようとする。
new Inner() と書くと、コンパイラは "No enclosing instance of type Outer is accessible" とエラーを出します。常に外部クラスのオブジェクト経由で内部クラスを生成してください: outer.new Inner()。
エラー 2: 内部クラス内に static フィールドやメソッドを宣言しようとする。
内部クラスは static メンバー(定数を除く)を持てません。必要なら static なネストクラス、つまり static 修飾子を付けたクラスを使いましょう。
エラー 3: 内部クラスの乱用。
内部クラスが外部クラスのフィールド/メソッドを使っていないなら、おそらくそれは static にすべきか、そもそも外に出すべきです。「念のため」で内部クラスを作らないでください。
エラー 4: 外部クラスのフィールド参照で混乱する。
内部クラスと外部クラスで同名のフィールドがある場合は、OuterClassName.this.field を使って外部クラスのフィールドを明示的に参照してください。
GO TO FULL VERSION