読者です 読者をやめる 読者になる 読者になる

四種類の内部クラス

Java にはクラス内にクラスを宣言する機能がある。
あるクラス内に宣言されたクラスを内部クラス(またはインナークラス)と呼ぶ。

内部クラスには以下の四種類があり、それぞれ特性が異なる。
・static メンバークラス
・非 static メンバークラス
・無名クラス
・ローカルクラス


それぞれの宣言方法と特性について記す。

static メンバークラス

public class OuterClass {
    //宣言
    static class InnerClass {
        private String name;
    }

    public OuterClass() {
        InnerClass ic = new InnerClass();

        //外部クラスからはプライベートフィールドにアクセスできる。
        ic.name = "innerclass!";
    }
}

使い方

public static void main( String[] args ) {
    //通常のクラスと同様にインスタンスを生成できる
    new InnerClass();

    //こういう宣言の仕方もOK
    new OuterClass.InnerClass();
}

非 static メンバークラス

public class OuterClass {
    //宣言
    class InnerClass {
        private String name;
    }

    public OuterClass() {
        InnerClass ic = new InnerClass();

        //外部クラスからはプライベートフィールドにアクセスできる。
        ic.name = "innerclass!";
    }
}

使い方

public static void main( String[] args ) 
{
    OuterClass outer = new OuterClass();

    //エンクロージングインスタンス名.new インナークラス名 でインスタンスを生成。
    InnerClass ic = outer.new InnerClass();
}

無名クラス

public class OuterClass {
    public OuterClass() {
        //宣言 Objectクラスを継承したクラスを宣言し、同時にインスタンス化している。
        Object o = new Object() {
                        @Override
                        public int hashCode() {
                            return 17;
                        }
                    };
    }
}


使い方

コンパレーターの作成時などに利用する。

Arrays.sort( stringArray, new Comparator<String>() {
    public int compare( String s1, String s2 ) {
        return s1.length() - s2.length();
    }
});

ローカルクラス

public class OuterClass {
    public List hogeMethod() {
        //メソッド内に宣言
        class MyList implements List {
            ....
        }
        
        return new MyList();
    }
}

使い方

ローカルクラスは、宣言したメソッド内以外からは使い回しができない。
これはクラスのスコープを狭めることにつながるので、場合によってはバグを減らすテクニックとして利用できる。

class MyCollection {
    public Iterator<E> iterator() {
        //このメソッド以外から MyIterator を利用しないなら、ローカルクラスとして宣言可能。
        class MyIterator implements Iterator<E> {
            ...
        }
        
        return new MyIterator();
    }
}

特徴

・static メンバークラスは通常のクラスとほとんど変わらない。通常のクラスと異なる点は、そのクラスを取り囲む外部クラスからは private フィールドにアクセスできる点。


・static メンバークラス以外のクラスは、暗黙的に外部クラスのインスタンスと密接な関連を持つ。この外部クラスのインスタンスをエンクロージングインスタンスと言う。

非 static メンバークラスとローカルクラスは、エンクロージングインスタンスのフィールドを操作できる。この内部クラスのインスタンスとエンクロージングインスタンスの関連は勝手に作られるため、プログラマが関連を見逃してしまい、思わぬ量のメモリを消費してしまうこともありうる。

また、エンクロージングインスタンスに null を代入しても内部クラスのインスタンスが生きていれば暗黙の関連は消えないため、プログラマの予想よりガベージコレクトされるタイミングが遅くなることがある。


・非 static メンバークラスに於いて、外部クラスと内部クラスに同名のフィールドが存在する場合は以下のようにして区別する。

public class OuterClass {
    //外部クラスでフィールドを宣言
    private String value;

    class InnerClass {
        //外部クラスと同名のフィールド
        private String value;

        public InnerClass() {
            //通常のようにフィールド名でアクセスする場合は内部クラスの value を参照。
            value = "inner class!";

            //外部クラスのフィールドを参照したい場合は以下のようにしてアクセスする。
            OuterClass.this.value = "outer class!";
        }
    }
}

同名のフィールドが存在しない場合は区別する必要が無いので、通常通りフィールド名で外部クラスのフィールドにアクセスできる。


・ローカルクラスは非 static メンバークラスと同じように外部クラスのフィールドに触れられる。
また、ローカルクラスは非 static メンバークラスに加え、ローカルクラスが宣言されたメソッド内の変数のうち final で宣言されたものについて参照のみ可能である。


・無名クラスのインスタンスに対して getClass() を呼ぶと以下のようになる。

  Object o = new Object() {};
  System.out.println( o.getClass() ); // class OuterClass$1

通常はこう。

  Object o = new Object();
  System.out.println( o.getClass() ); // class java.lang.Object

まとめ

内部クラスでなければできない実装は存在せず、大きな利点は無いのであまり多用しないように。