1. 無名クラス入門
クラス Animal があり、動物のふるまいを表しているとします。このクラスにはメソッド say() があり、"動物は音を発する" と出力します。犬のようにふるまい、"ワン!" と出力するオブジェクトを作りたいが、そのためだけに別ファイル Dog.java を作りたくないとします。
そこで役立つのが無名クラスです。無名クラスは名前のないクラスで、使うその場で宣言・生成します。🚀 別ファイルを作らずにその場で継承を「その場で」行えます。プロジェクトの中に一度しか使わない小さなクラスのファイルを量産する代わりに、必要な場所にロジックを直接書けます。
構文はどう書く?
無名クラスの構文は一見変わって見えますが、実はとてもシンプルです。
TipPeremennoy imya = new TipDlyaNasledovaniya() {
// ここに無名クラスの本体を書く
// 親クラスのメソッドをオーバーライドする
};
この構文を分解して説明します。
- TipPeremennoy imya: 新しいオブジェクトを保存するための通常の変数宣言です。
- new TipDlyaNasledovaniya(): まるで通常のオブジェクトを生成しているかのように見えますが、ここでは継承したいクラス名をそのまま使います。丸括弧 () の後にセミコロン ! は付きません。
- { ... }: ここで波括弧を開き、無名クラスのロジックを書きます。親クラスのメソッドをオーバーライドしたり、自分の処理を追加したりできます。
通常のクラスからの継承の例:
動物の例に戻りましょう。
class Animal {
void say() {
System.out.println("動物は音を発する");
}
}
// 無名クラスを作成し、Animal を継承する
Animal dog = new Animal() {
// say() をオーバーライド
@Override
void say() {
System.out.println("ワンワン! 🐶");
}
};
Animal cat = new Animal() {
@Override
void say() {
System.out.println("ニャーニャー! 🐱");
}
};
dog.say(); // 出力: ワンワン! 🐶
cat.say(); // 出力: ニャーニャー! 🐱
この例では dog と cat の2つのオブジェクトを作りました。これらは本質的に Animal を継承する無名クラスです。それでも別ファイルは1つも作っていません。
アプローチの比較
// 通常のやり方(別クラスを作る)
class Dog extends Animal {
@Override
void say() { System.out.println("ワン!"); }
}
Animal dog = new Dog();
// VS
// 無名クラス(すべて同じ場所に書く)
Animal dog = new Animal() {
@Override
void say() { System.out.println("ワン!"); }
};
結果は同じですが、無名クラスの方が短く、プロジェクトを散らかしません。
2. コンパイル後の無名クラス名
無名クラスはソースコード上では名前を持ちませんが、Java のコンパイラは .class ファイルを生成するために、規則に従って名前を付けます。
- 無名クラスのファイル名は、それが宣言された外側のクラスの名前から始まります.
- 外側のクラス名の後にドル記号 $ が続きます。
- そのファイル内での無名クラスの通し番号が、1 から付与されます。
たとえば動物の例が Main.java にあるなら、コンパイル後には次の3つのファイルが生成されます。
- Main.class
- Main$1.class(犬用の無名クラス)
- Main$2.class(猫用の無名クラス)
無名クラスが、内部クラスにあるメソッドの中で宣言されている場合、名前はさらに複雑になり、たとえば OuterClass$InnerClass$1.class のようになります。
これはコンパイラの内部規約で、知っておくと便利ですが、日常の開発で重要になることは多くありません。大事なのは、無名クラスもソース上で名前がないだけで、立派なクラスであるという点です。
3. 重要な特徴と制約
無名クラスは強力ですが、独自のルールがあります。
変数へのアクセス。 無名クラスは、囲んでいるメソッドのローカル変数を使えます。ただし、それらの変数は final または effectively final(初期化後に値が変更されない)でなければなりません。
public void doSomething() {
String greeting = "こんにちは!"; // この変数は effectively final
class OuterClass {
void greet() {
// メソッド内で無名クラスを作成
new Object() {
void sayHello() {
System.out.println(greeting); // これは許可される
// greeting = "さようなら!"; // これはコンパイルエラーになる!
}
}.sayHello();
}
}
new OuterClass().greet();
}
なぜこうなるのでしょうか。無名クラスはメソッド本体より長く「生きる」可能性があり、もしそれが変数を書き換えられると問題が生じるためです。
コンストラクタはない。 無名クラスには名前がないため、コンストラクタも作れません。ただし、オブジェクト生成時に実行するコードは、初期化ブロックで書けます。
Animal dog = new Animal() {
// 初期化ブロック
{
System.out.println("無名クラスの初期化 🐶");
}
@Override
void say() {
System.out.println("ワンワン!");
}
};
制約。 無名クラスでは静的フィールド(定数を除く)や静的メソッドを宣言できません。また常にほかのオブジェクトの一部として生成されるため、static、public、protected、private を付けることもできません。
4. 役立つ豆知識
無名クラスを使うべきとき
- クラスの継承(またはインターフェース実装)が1回だけ必要。
- 実装が小さい — メソッドは1〜2個、十数行程度。
- その場所でしか使わないため、クラスに名前を付ける意味が薄い。
- 使い捨ての小さなクラスでパッケージを「散らかしたくない」。
典型的なユースケース:
- イベントハンドラ(GUI、Swing、Android など)。
- メソッドへのコールバック(callback)の受け渡し。
- コレクションのソート用に、コンパレータの即席実装。
- 一時的に振る舞いを変えたオブジェクト(テスト用など)。
5. 外側のクラスとの相互作用
無名クラスが外側のクラスの非 static なメソッドやブロック内で宣言されている場合、その外側のクラスのフィールドやメソッド(private も含む)にアクセスできます。
public class Outer {
private String secret = "秘密のテキスト";
// 基底クラス
class Printer {
public void print() {
System.out.println("通常の出力");
}
}
public void revealSecret() {
Printer p = new Printer() {
@Override
public void print() {
System.out.println("プライベートへのアクセス: " + secret);
}
};
p.print();
}
public static void main(String[] args) {
new Outer().revealSecret();
}
}
6. 無名クラスでよくある間違い
エラー No.1: 囲んでいるメソッドの変数を変更しようとする。
無名クラスの外で宣言した変数を、無名クラス内で使った後に変更しようとすると、コンパイルエラーになります。変数は final か effectively final(初期化後に変更しない)である必要があります。
エラー No.2: 無名クラスが大きくなりすぎる。
無名クラスが数十行、メソッドも複数になってきたら、別の名前付きクラスに切り出すサインです。さもないと読みづらくなります。
エラー No.3: 静的メソッドやフィールドを使おうとする。
無名クラスでは静的メソッドやフィールド(定数を除く)を宣言できません。本当に必要なら通常の入れ子クラスを作りましょう。
エラー No.4: スコープを忘れる。
無名クラスは宣言したその場所でしか見えず、名前もありません。繰り返し使う必要があるなら、通常のクラスを宣言してください。
GO TO FULL VERSION