Kotlinの入れ子クラスと内部クラス

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

1. 前書き

このチュートリアルでは、Kotlinにネストされた内部クラスを作成する4つの方法を見ていきます。

2. Javaとの簡単な比較

Java nested classesについて考えている人のために、関連する用語を簡単に要約してみましょう。

コトリン

Java

内部クラス

非静的ネストクラス

ローカルクラス

ローカルクラス

匿名オブジェクト

匿名クラス

入れ子クラス

静的なネストされたクラス

確かに同一ではありませんが、それぞれの機能とユースケースについて考えるとき、この表をガイドとして使用できます。

3. 内部クラス

First, we can declare a class inside another class using the keyword inner

これらのクラスhave access to members of the enclosing class, even private members

これを使用するには、最初に外部クラスのインスタンスを作成する必要があります。それなしでは内部クラスを使用できません。

Computerクラス内にHardDisk内部クラスを作成しましょう。

class Computer(val model: String) {
    inner class HardDisk(val sizeInGb: Int) {
        fun getInfo() = "Installed on ${[email protected]} with $sizeInGb GB"
    }
}

qualified this expressionを使用してComputerクラスのメンバーにアクセスすることに注意してください。これは、JavaでHardDiskに相当するComputer.thisを実行する場合と同様です。

それでは、実際の動作を見てみましょう。

@Test
fun givenHardDisk_whenGetInfo_thenGetComputerModelAndDiskSizeInGb() {
    val hardDisk = Computer("Desktop").HardDisk(1000)
    assertThat(hardDisk.getInfo())
      .isEqualTo("Installed on Computer(model=Desktop) with 1000 GB")
}

4. ローカルインナークラス

Next, we can define a class inside a method’s body or in a scope block

それがどのように機能するかを簡単な例で見てみましょう。

まず、ComputerクラスのpowerOnメソッドを定義しましょう。

fun powerOn(): String {
    //...
}

powerOnメソッド内で、Ledクラスを宣言し、点滅させましょう。

fun powerOn(): String {
    class Led(val color: String) {
        fun blink(): String {
            return "blinking $color"
        }
    }
    val powerLed = Led("Green")
    return powerLed.blink()
}

Ledクラスのスコープは、メソッド内にのみあることに注意してください。

With local inner classes, we can access and modify variables declared in the outer scopepowerOnメソッドにdefaultColorを追加しましょう。

fun powerOn(): String {
    var defaultColor = "Blue"
    //...
}

それでは、LedクラスにchangeDefaultPowerOnColorを追加しましょう。

class Led(val color: String) {
    //...
    fun changeDefaultPowerOnColor() {
        defaultColor = "Violet"
    }
}
val powerLed = Led("Green")
log.debug("defaultColor is $defaultColor")
powerLed.changeDefaultPowerOnColor()
log.debug("defaultColor changed inside Led " +
  "class to $defaultColor")

どの出力:

[main] DEBUG c.b.n.Computer - defaultColor is Blue
[main] DEBUG c.b.n.Computer - defaultColor changed inside Led class to Violet

5. 匿名オブジェクト

Anonymous objects can be used to define an implementation of an interface or an abstract class without creating a reusable implementation

Kotlinの匿名オブジェクトとJavaの匿名内部クラスの大きな違いは、anonymous objects can implement multiple interfaces and methods.

まず、ComputerクラスにSwitcherインターフェイスを追加しましょう。

interface Switcher {
    fun on(): String
}

それでは、powerOnメソッド内にこのインターフェースの実装を追加しましょう。

fun powerOn(): String {
    //...
    val powerSwitch = object : Switcher {
        override fun on(): String {
            return powerLed.blink()
        }
    }
    return powerSwitch.on()
}

ご覧のとおり、匿名のpowerSwitchオブジェクトを定義するには、object expressionを使用します。 また、オブジェクト式が呼び出されるたびに、オブジェクトの新しいインスタンスが作成されることを考慮する必要があります。

内部クラスのような匿名オブジェクトを使用すると、スコープ内で以前に宣言された変数を変更できます。 これは、KotlinにはJavaで期待される実質的な最終制限がないためです。

それでは、PowerSwitch オブジェクトにchangeDefaultPowerOnColorを追加して、次のように呼び出します。

val powerSwitch = object : Switcher {
    //...
    fun changeDefaultPowerOnColor() {
        defaultColor = "Yellow"
    }
}
powerSwitch.changeDefaultPowerOnColor()
log.debug("defaultColor changed inside powerSwitch " +
  "anonymous object to $defaultColor")

次のような出力が表示されます。

...
[main] DEBUG c.b.n.Computer - defaultColor changed inside powerSwitch anonymous object to Yellow

また、オブジェクトが単一の抽象メソッドを持つインターフェイスまたはクラスのインスタンスである場合にも注意してください。 lambda expressionを使用して作成できます。

6. 入れ子クラス

そして最後に、we can define a class inside another class without the keyword inner

class Computer(val model: String) {
    class MotherBoard(val manufacturer: String)
}

In this type of class, we don’t have access to the outer class instance.ただし、囲んでいるクラスのcompanion objectメンバーにアクセスできます。

それでは、Computerクラス内にcompanion objectを定義して、それを確認しましょう。

companion object {
    const val originCountry = "China"
    fun getBuiltDate(): String {
        return "2018-07-15T01:44:25.38Z"
    }
}

次に、MotherBoard内のメソッドを使用して、そのメソッドと外部クラスに関する情報を取得します。

fun getInfo()
  = "Made by $manufacturer - $originCountry - ${getBuiltDate()}"

これで、どのように機能するかをテストできます。

@Test
fun givenMotherboard_whenGetInfo_thenGetInstalledAndBuiltDetails() {
    val motherBoard = Computer.MotherBoard("MotherBoard Inc.")
    assertThat(motherBoard.getInfo())
      .isEqualTo(
        "Made by MotherBoard Inc. installed in China - 2018-05-23")
}

ご覧のとおり、ComputerクラスのインスタンスなしでmotherBoardを作成します。

7. 結論

この記事では、Kotlinでネストされたクラスと内部クラスを定義および使用して、コードをより簡潔でカプセル化する方法を見てきました。

また、対応するJavaの概念といくつかの類似点があります。

このチュートリアルの完全に機能する例は、over on GitHubにあります。