覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Kotlin - 内部クラスとネストされたクラス

Java では クラスの中にクラスを宣言する事が可能です。
あるクラスの一部機能を切り出して、クラス化してその外側のクラスからしか利用しない といったケースで使う事があります。

例えば以下のように記述できます。

public class Sample {

    private final String name;

    public Sample(String name) {
        this.name = name;
    }

    private class InnerClazz {

        // 外側のクラスのインスタンス変数にアクセスできる
        // 明確に記述しなくても外側のクラスのインスタンスにアクセスできてしまう
        public void helloOuterName() {
            System.out.println(Sample.this.name);
        }
    }
}

しかし、この内部クラスとて宣言した InnerClazz は、エンクロージングクラス( = 内部クラスからみて外側にあるクラス)のインスタンスにアクセスできてしまいます。
上記のコード例からも private なメンバへアクセス可能です。

このような状況を意図しない場合には、基本的に内側のクラスに static 修飾子を付与してネストしたクラスという扱いにする事で、
エンクロージングクラスへの暗黙的な参照は無くなります。

以下コードは、コンパイルが通らなくなります。

public class Sample {

    private final String name;

    public Sample(String name) {
        this.name = name;
    }

    private static class InnerClazz {

        // コンパイルが通らない
        public void helloOuterName() {
            System.out.println(Sample.this.name);
        }
    }
}

コンパイルが通るようにするためには、例えば以下のようなコードにする必要があります。

public class Sample {

    private final String name;

    public Sample(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    private static class InnerClazz {

        private final Sample sample;

        // 明示的にインスタンスを渡す必要がある
        private InnerClazz(Sample sample) {
            this.sample = sample;
        }
        
        public void helloOuterName() {
            System.out.println(sample.getName());
        }
    }
}

一般的に、ほとんどのケースでクラスの中にクラスを宣言する場合、 static 修飾子を付与したネストクラスが必要とされるように感じます。


Kotlinの場合

Kotlin の場合は、明示的な修飾子を付与しない内部クラスは、Javaにおける static 修飾子が付与されたネストされたクラスと同等です。
そのため、暗黙的に内側のクラスは、外側のクラスへの参照を持っていません。

class KotlinSample(val name : String) {

    private class InnterClazz(val sample : KotlinSample) {
        fun helloOuterName() {
            println(sample.name)
        }
    }
}


逆に、内部クラスが外側のクラスへの参照を持った状態にするには、内部クラスに innrer 修飾子を付与する必要があります。
この内部クラスから外部クラスへアクセスするためには、 this@外部クラス名 という記述をする必要があります。

以下がサンプルコードになります。

class KotlinSample(val name : String) {

    private inner class InnterClazz() {
        fun helloOuterName() {
            println(this@KotlinSample.name)
        }
    }
}

JavaとKotlinのネストされたクラスと内部クラスの対応関係は以下の通りです

OutAクラスの内部で宣言されたInBクラス Javaでの宣言 Kotlinでの宣言
ネストされたクラス(外部クラスの参照を保持しない) static class InB class InB
内部クラス(外部クラスへの参照を保持する) class InB inner class InB


まとめ

Javaであるクラスの中に別のクラスを記述する場合、外側へのクラスの暗黙的な参照を必要とするケースはほとんどなく、
基本的に内側のクラスに static 修飾子をつけることになります。
Kotlin では、内側のクラスに何の修飾子もつけない状態が、これと同等になっているのは理にかなっているように感じました。