Classi annidate e interne di Kotlin

1. Introduzione

In questo tutorial, esamineremo quattro modi per creare classi nidificate e interne in Kotlin.

2. Confronto rapido con Java

Per coloro che pensano alle classi nidificate Java, facciamo una rapida carrellata dei termini correlati:

Kotlin Giava
Classi interne Classi annidate non statiche
Classi locali Classi locali
Oggetti anonimi Classi anonime
Classi annidate Classi annidate statiche

Anche se certamente non identica, possiamo usare questa tabella come guida quando pensiamo alle capacità e ai casi d'uso per ciascuno.

3. Classi interne

Innanzitutto, possiamo dichiarare una classe all'interno di un'altra classe utilizzando la parola chiave inner .

Queste classi hanno accesso ai membri della classe inclusa, anche ai membri privati .

Per usarlo, dobbiamo prima creare un'istanza della classe esterna; non possiamo usare classi interne senza di essa.

Creiamo una classe interna HardDisk all'interno di una classe Computer :

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

Nota che usiamo un'espressione qualificata per accedere ai membri della classe Computer , che è simile a quando facciamo Computer.this nell'equivalente Java di HardDisk .

Vediamolo ora in azione:

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

4. Classi interne locali

Successivamente, possiamo definire una classe all'interno del corpo di un metodo o in un blocco di ambito .

Facciamo un rapido esempio per vedere come funziona.

Per prima cosa, definiamo un metodo powerOn per la nostra classe Computer :

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

All'interno del metodo powerOn dichiariamo una classe Led e la facciamo lampeggiare:

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

Si noti che l'ambito della classe Led è solo all'interno del metodo.

Con le classi interne locali, possiamo accedere e modificare le variabili dichiarate nell'ambito esterno . Aggiungiamo un defaultColor nel metodo powerOn :

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

Ora aggiungiamo un changeDefaultPowerOnColor nella nostra classe 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")

Quali uscite:

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

5. Oggetti anonimi

Gli oggetti anonimi possono essere utilizzati per definire un'implementazione di un'interfaccia o una classe astratta senza creare un'implementazione riutilizzabile .

Una grande differenza tra oggetti anonimi in Kotlin e classi interne anonime in Java è che gli oggetti anonimi possono implementare più interfacce e metodi.

Innanzitutto, aggiungiamo un'interfaccia Switcher nella nostra classe Computer :

interface Switcher { fun on(): String }

Ora aggiungiamo un'implementazione di questa interfaccia all'interno del metodo powerOn :

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

Come possiamo vedere, per definire il nostro oggetto powerSwitch anonimo usiamo un'espressione oggetto. Inoltre, dobbiamo tenere conto del fatto che ogni volta che viene richiamata l'espressione dell'oggetto viene creata una nuova istanza dell'oggetto.

Con oggetti anonimi come le classi interne, possiamo modificare le variabili dichiarate in precedenza nello scope. Questo perché Kotlin non ha la restrizione finale effettiva che ci si aspetta da Java.

Ora aggiungiamo un changeDefaultPowerOnColor nel nostro oggetto PowerSwitch e chiamiamolo:

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

Vedremo un output come questo:

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

Inoltre, nota che se il nostro oggetto è un'istanza di un'interfaccia o di una classe con un unico metodo astratto; possiamo crearlo usando un'espressione lambda.

6. Classi annidate

Infine, possiamo definire una classe all'interno di un'altra classe senza la parola chiave 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. But, we can access companion object members of the enclosing class.

So, let's define a companion object inside our Computer class to see it:

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

And then a method inside MotherBoard to get information about it and the outer class:

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

Now, we can test it to see how it works:

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

As we can see, we create motherBoard without an instance of Computer class.

7. Conclusion

In this article, we’ve seen how to define and use nested and inner classes in Kotlin to make our code more concise and encapsulated.

Also, we've seen some similarities to the corresponding Java concepts.

Un esempio completamente funzionante per questo tutorial può essere trovato su GitHub.