Utilizzare le extension di Kotlin

Vi è mai capitato di lavorare con una libreria di terze parti ed avere la necessità di aggiungere funzionalità o implementare “comportamenti” custom utili alle vostre esigenze?

O magari di ritrovarvi a lavorare ad un progetto pieno zeppo di classi di utilità che sopperiscono alle mancanze della Java API o delle succitate librerie di terze parti…

Kotlin permettere di risolvere queste due problematiche in maniera estremamente elegante, senza dover ricorre ad ereditarietà o classi statiche.

La soluzione si chiama extension.

Extension function

Le extension function permettono di aggiungere funzionalità a classi “di altri” senza scomodare l’ereditarietà. Tutto questo in modo estremamente espressivo e leggibile.

Partiamo con un esempio molto concreto: di recente, lavorando con i repository di Spring Data, mi sono reso conto che alcuni metodi restituiscono oggetti Optional (ad es. findById), utilissimi in Java, ma poco pratici se stiamo lavorando con Kotlin.

Ho quindi implementato una funzione d’estensione per convertire un Optional in un tipo nullable:

fun <T : Any> Optional<T>.toNullable(): T? = this.orElse(null)

Osserviamo la dichiarazione appena riportata:

  • Il tipo Optional in questo caso si definisce receiver type — o semplicemente receiver —, ovvero, il tipo che viene esteso — che riceve l’estensione.

  • All’interno del corpo della funzione ci riferiamo all’istanza del receiverreceiver object — con la parola chiave this, proprio come se stessimo definendo la nuova funzione all’interno della classe Optional — o di una classe da essa ereditata.

Ma la cosa veramente ganza è il fatto di poter invocare questa funzione esattamente come se fosse parte della classe originaria:

val nullableVal = optionalValue.toNullable()

NOTA In realtà, ho poi scoperto che Spring Data, dalla versione 2.1.4, include una fantastica extension function per i suoi CrudRepository che permette di lavorare con i tipi nullable: la funzione è findByIdOrNull.

Extension property

Esattamente come per le funzioni è possibile estendere una qualunque classe con delle property custom:

val Int.double: Int
    get() = this * 2

Il campo double così definito sarà ora disponibile per tutti gli oggetti di tipo Int:

val intValue = 3
val doubleOf3 = intValue.double // => 6

Il fatto che nella property abbia definito solo il get non è un caso, infatti le extension property non supportano l’assegnazione. Il perché di questo appare chiaro una volta capito che…

… le extension sono metodi statici

Proprio così: definendo un’estensione non andiamo ad aggiungere effettivamente una nuova funzione membro in una classe. Piuttosto — dietro le quinte — andiamo a definire un metodo statico che accetta il receiver object come suo primo argomento.

Vediamo un altro esempio per capire meglio. Supponiamo di definire la seguente extension function nel file Utilities.kt:

fun String.withAnAsterisk() = "$this*" // ... non sapevo cosa inventarmi...

che ovviamente invocheremo così:

val str = "test".withAnAsterisk()

Se però volessimo usare questa funzione in un metodo Java ci accorgeremmo subito della sua natura “statica”, infatti dovremmo invocarla in questo modo:

String str = UtilitiesKt.withAnAsterisk("test")

Allo stesso modo, per invocare una extension property in Java avremo qualcosa di simile:

int doubleInt = IntUtilsKt.getDouble(10)

In questo caso ho supposto di voler utilizzare la property definita qualche paragrafo fa — come vedete per accedere alla proprietà dobbiamo ricorrere esplicitamente al suo getter.

In conclusione

Le extension rappresentano sicuramente un’interessante peculiarità di Kotlin, ma come molti altri aspetti del linguaggio, che a prima vista sembrano una panacea — soprattutto per chi è abituato a lavorare con Java —, vanno usate con parsimonia e saggezza.

Un abuso indiscriminato di funzioni e property d’estensione può portare a codice frammentato e poco coerente; in alcuni casi può aver senso ricorrere alla vecchia, cara — e forse fin troppo bistrattata — ereditarietà.

Alla prossima,

David