Classes imbriquées et internes de Kotlin

1. Introduction

Dans ce didacticiel, nous examinerons quatre façons de créer des classes imbriquées et internes dans Kotlin.

2. Comparaison rapide avec Java

Pour ceux qui pensent aux classes imbriquées Java, faisons un bref aperçu des termes associés:

Kotlin Java
Classes intérieures Classes imbriquées non statiques
Classes locales Classes locales
Objets anonymes Classes anonymes
Classes imbriquées Classes imbriquées statiques

Bien que ce ne soit certainement pas identique, nous pouvons utiliser ce tableau comme guide pour réfléchir aux capacités et aux cas d'utilisation de chacun.

3. Classes internes

Tout d'abord, nous pouvons déclarer une classe dans une autre classe en utilisant le mot-clé inner .

Ces classes ont accès aux membres de la classe englobante, même aux membres privés .

Pour l'utiliser, nous devons d'abord créer une instance de la classe externe; nous ne pouvons pas utiliser de classes internes sans cela.

Créons une classe interne HardDisk dans une classe Computer :

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

Notez que nous utilisons une expression qualifiée pour accéder aux membres de la classe Computer , ce qui est similaire à lorsque nous faisons Computer.this dans l'équivalent Java de HardDisk .

Maintenant, voyons-le en action:

@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 internes locales

Ensuite, nous pouvons définir une classe dans le corps d'une méthode ou dans un bloc de portée .

Faisons un exemple rapide pour voir comment cela fonctionne.

Tout d'abord, définissons une méthode powerOn pour notre classe Computer :

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

À l'intérieur de la méthode powerOn , déclarons une classe Led et faisons-la clignoter:

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

Notez que la portée de la classe Led est uniquement à l'intérieur de la méthode.

Avec les classes internes locales, nous pouvons accéder et modifier les variables déclarées dans la portée externe . Ajoutons un defaultColor dans la méthode powerOn :

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

Maintenant, ajoutons un changeDefaultPowerOnColor dans notre 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")

Quelles sorties:

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

5. Objets anonymes

Les objets anonymes peuvent être utilisés pour définir une implémentation d'une interface ou d'une classe abstraite sans créer d'implémentation réutilisable .

Une grande différence entre les objets anonymes dans Kotlin et les classes internes anonymes en Java est que les objets anonymes peuvent implémenter plusieurs interfaces et méthodes.

Tout d'abord, ajoutons une interface Switcher dans notre classe Computer :

interface Switcher { fun on(): String }

Maintenant, ajoutons une implémentation de cette interface dans la méthode powerOn :

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

Comme nous pouvons le voir, pour définir notre objet powerSwitch anonyme, nous utilisons une expression d'objet. En outre, nous devons prendre en compte que chaque fois que l'expression d'objet est appelée, une nouvelle instance de l'objet est créée.

Avec des objets anonymes comme les classes internes, nous pouvons modifier les variables précédemment déclarées dans la portée. C'est parce que Kotlin n'a pas la restriction finale à laquelle nous nous attendions en Java.

Maintenant, ajoutons un changeDefaultPowerOnColor dans notre objet PowerSwitch et appelons-le:

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

Nous verrons une sortie comme celle-ci:

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

Notez également que si notre objet est une instance d'une interface ou une classe avec une seule méthode abstraite; nous pouvons le créer en utilisant une expression lambda.

6. Classes imbriquées

Et enfin, nous pouvons définir une classe à l'intérieur d'une autre classe sans le mot clé 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 exemple entièrement fonctionnel de ce didacticiel est disponible à l'adresse over sur GitHub.