[Kotlin]内部クラス – 入れ子クラスとの違いとは?

クラスには様々なクラスをネスト(入れ子に)することができますが、その中でも基本的なものが「通常のクラス」と「内部クラス(インナークラス)」です。

今回はこの2種類のネストを比較して、両者の違いを明らかにしたいと思います。

定義構文で比較

クラス – クラス

まずはクラスに通常のクラスを入れてみましょう。

class Toyota{
    val car = "Prius"
    fun printPrice() = "価格は256万5千円です"

    //DaihatsuはToyotaにネストされたクラス
    class Daihatsu{
        val car = "Tanto"
        fun printPrice() = "価格は124万3千円です"
    }
}

ただ単にクラスをクラスの中に入れただけの、単純なクラス – クラスのネストです。

クラス – 内部クラス

では次に、内部クラスをネストします。

class Toyota{
    val car = "Prius"
    fun printPrice() = "価格は256万5千円です"

    inner class Daihatsu{  //内側のクラスに「inner」を付ける
        val car = "Tanto"
        fun printPrice() = "価格は124万3千円です"
    }
}

内側のクラスの頭に「inner」が付いただけ。これで内部クラスが定義できます。

インスタンス化で比較

次にこの2つの内側クラスをインスタンス化していきます。クラスと内部クラスで何が違うのでしょうか?

クラス – クラス

まず通常のクラス。

fun main(){
    //内側クラスのインスタンスを直接生成
    val daihatsu = Toyota.Daihatsu()

    println(daihatsu.printPrice())
}
//価格は124万3千円です

コードの2行目では、Toyota.Daihatsu()で内側クラスのインスタンスを生成しています。
これは「Toyotaクラスの中にあるDaihatsuクラス」のコンストラクタを呼び出しているだけ。つまり外側クラスのコンストラクタは呼び出されていません。

クラス – 内部クラス

では内部クラスはどうでしょう?

fun main(){
    //内側クラスのインスタンスを生成するには、外部クラスのインスタンスが必要
    val x = Toyota().Daihatsu()

    println(x.printPrice())
}
//価格は124万3千円です

内部クラスのインスタンスを生成するのにToyota()と( )が付いています。
これは外側クラスのコンストラクタです。内部クラスのインスタンスは、外側クラスのインスタンス経由でしか生成できないことに注意してください。

メンバへのアクセスで比較

最後にクラス定義内で、他方のクラスメンバを参照してみましょう。こちらもクラスと内部クラスでは大きな違いがあります。

クラス – クラス

まずは通常のクラス。内側から外側のメンバを参照してみると、

class Toyota{

    //privateなメンバ
    private val car = "Prius"

    fun printPrice() = "価格は256万5千円です"

    class Daihatsu{
        val car = car     //Error
        fun printPrice() = Toyota.printPrice()   //Error

        //外側クラスのインスタンスを生成すればアクセス可能
        val car = Toyota().car
        fun printPrice() = Toyota().printPrice()
    }
}

9行目と10行目はエラー。クラス – クラスの場合、インスタンス無しでは他方のメンバを参照できません

立場を逆にして、外側から内側のメンバを参照しても結果は同じ。他方のメンバを参照するには、そのクラスのインスタンスが必要です。

class Toyota{
    val car = "Prius"

    //Error privateメンバにはアクセス不可
    val daihatsuCar = Daihatsu().car  //Error

    fun daihatsuPrice() = Daihatsu().printPrice()

    private class Daihatsu{
        private val car = "Tanto"
        fun printPrice() = "価格は124万3千円です"
    }
}

privateメンバへのアクセス

上記2つの例では、お互いのprivateメンバにアクセスしています。しかし有効なのは前者のみです。

クラス – クラスの場合、外側→内側のprivateメンバへのアクセスはErrorとなります。

クラス – 内部クラス

次に内部クラスです。内部クラスはクラスとは違い、常に外側のクラスメンバへの参照を持つという点に注目しましょう。

class Toyota{

    //privateなメンバ
    private val car = "Prius"

    fun printPrice() = "価格は256万5千円です"

    //内側クラスのメンバ参照はクラスのときと同じ。privateメンバにはアクセス不可
    val daihatsuCar = Daihatsu().car

    private inner class Daihatsu{

        //外側クラスのメンバは「this@外側クラス.〇〇」でアクセス
        val car = this@Toyota.car
        fun printPrice() = this@Toyota.printPrice()

        //自身のメンバを参照するには「this」のみでアクセス
        val x = this.car
    }
}

外側クラスから内側クラスのメンバへは先ほどと同じく、インスタンス経由でアクセスしています。対して内側クラスから外側クラスのメンバへはインスタンスを使わず、「this@Toyota.〇〇」でアクセスしています。

このように内部クラスはあくまで外側クラスの一部として振る舞うという点が、通常のクラスとの大きな違いです。

比較のまとめ

クラス – クラスのネスト

  • 内側クラスのインスタンス化に、外側クラスのインスタンスは不要
  • 他方のクラスメンバを参照するには、そのクラスのインスタンスが必要

クラス – 内部クラスのネスト

  • インナークラスは外側クラスのインスタンスが無いと、自身のインスタンスを生成できない
  • インナークラスは外側クラスへの参照を持つ
タイトルとURLをコピーしました