Classes aninhadas e internas do Kotlin

Classes aninhadas e internas do Kotlin

1. Introdução

Neste tutorial, veremos quatro maneiras de criar classes aninhadas e internas emKotlin.

2. Comparação rápida com Java

Para aqueles que estão pensando emJava nested classes, vamos fazer um rápido resumo dos termos relacionados:

Kotlin

Java

Classes internas

Classes aninhadas não estáticas

Aulas locais

Aulas locais

Objetos anônimos

Classes anônimas

Classes aninhadas

Classes aninhadas estáticas

Embora certamente não seja idêntico, podemos usar esta tabela como um guia ao pensar sobre os recursos e casos de uso de cada um.

3. Classes internas

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

Essas classeshave access to members of the enclosing class, even private members.

Para usá-lo, precisamos primeiro criar uma instância da classe externa; não podemos usar classes internas sem ele.

Vamos criar uma classe internaHardDisk dentro de uma classeComputer:

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

Observe que usamosqualified this expression para acessar membros da classeComputer, que é semelhante a quando fazemosComputer.this no equivalente Java deHardDisk.

Agora, vamos ver em ação:

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

4. Classes internas locais

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

Vamos dar um exemplo rápido para ver como funciona.

Primeiro, vamos definir um métodopowerOn para nossa classeComputer:

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

Dentro do métodopowerOn, vamos declarar uma classeLed e fazê-la piscar:

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

Observe que o escopo da classeLed está apenas dentro do método.

With local inner classes, we can access and modify variables declared in the outer scope. Vamos adicionar umdefaultColor no métodopowerOn:

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

Agora, vamos adicionar umchangeDefaultPowerOnColor em nossa classeLed:

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

Quais saídas:

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

5. Objetos anônimos

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

Uma grande diferença entre objetos anônimos em Kotlin e classes internas anônimas em Java é queanonymous objects can implement multiple interfaces and methods.

Primeiro, vamos adicionar uma interfaceSwitcher em nossa classeComputer:

interface Switcher {
    fun on(): String
}

Agora, vamos adicionar uma implementação desta interface dentro do métodopowerOn:

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

Como podemos ver, para definir nosso objetopowerSwitch anônimo, usamos umobject expression. Além disso, precisamos levar em consideração que toda vez que a expressão do objeto é chamada, uma nova instância do objeto é criada.

Com objetos anônimos, como classes internas, podemos modificar variáveis ​​declaradas anteriormente no escopo. Isso ocorre porque Kotlin não tem a restrição efetivamente final que esperamos em Java.

Agora, vamos adicionar umchangeDefaultPowerOnColor em nossoPowerSwitch objeto e chamá-lo de:

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

Veremos uma saída como esta:

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

Além disso, observe que se nosso objeto for uma instância de uma interface ou uma classe com um único método abstrato; podemos criá-lo usando umlambda expression.

6. Classes aninhadas

E por último,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. Mas, podemos acessarcompanion object membros da classe envolvente.

Então, vamos definir umcompanion object dentro de nossa classeComputer para vê-lo:

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

E então um método dentro deMotherBoard para obter informações sobre ele e a classe externa:

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

Agora, podemos testá-lo para ver como funciona:

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

Como podemos ver, criamosmotherBoard sem uma instância da classeComputer.

7. Conclusão

Neste artigo, vimos como definir e usar classes internas e aninhadas no Kotlin para tornar nosso código mais conciso e encapsulado.

Além disso, vimos algumas semelhanças com os conceitos Java correspondentes.

Um exemplo totalmente funcional para este tutorial pode ser encontradoover on GitHub.