Вложенные и внутренние классы Kotlin

Kotlin Вложенные и Внутренние Классы

1. Вступление

В этом руководстве мы рассмотрим четыре способа создания вложенных и внутренних классов вKotlin.

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.

Чтобы использовать его, нам нужно сначала создать экземпляр внешнего класса; без него мы не сможем использовать внутренние классы.

Давайте создадим внутренний классHardDisk внутри классаComputer:

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

Обратите внимание, что мы используемqualified this expression для доступа к членам классаComputer, что аналогично тому, как мы делаемComputer.this в Java-эквивалентеHardDisk.

Теперь давайте посмотрим, как это работает:

@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.

Давайте сделаем небольшой пример, чтобы увидеть, как это работает.

Во-первых, давайте определим методpowerOn для нашего классаComputer:

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 scope. ДобавимdefaultColor в методpowerOn:

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

Теперь давайте добавимchangeDefaultPowerOnColor в наш классLed:

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.

Во-первых, давайте добавим интерфейсSwitcher в наш классComputer:

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.

Теперь давайте добавимchangeDefaultPowerOnColor в наш объектPowerSwitch и назовем его:

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 членам включающего класса.

Итак, давайте определимcompanion object внутри нашего классаComputer, чтобы увидеть его:

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")
}

Как видим, мы создаемmotherBoard без экземпляра классаComputer.

7. Заключение

В этой статье мы увидели, как определять и использовать вложенные и внутренние классы в Kotlin, чтобы сделать наш код более кратким и инкапсулированным.

Кроме того, мы заметили некоторое сходство с соответствующими концепциями Java.

Полностью рабочий пример для этого руководства можно найтиover on GitHub.