Introduzione alla null safety in Kotlin

Una delle caratteristiche di Kotlin, tra le più interessanti, è la cosiddetta null safety, ovvero la verifica in fase di compilazione delle null reference presenti nel nostro codice.

Avete presente quelle simpatiche eccezioni di tipo NullPointerException che saltano fuori a runtime, quando meno ce lo aspettiamo, e ci fanno esclamare “accipicchia!” (o qualcosa di simile…), con tono un po’ imbarazzato?

Ecco, Kotlin elimina (quasi) completamente quest’imbarazzo tramite la definizione di tipi nullable e non-null.

Cosa significa?

Significa che avremo la possibilità di dichiarare variabili che possono essere valorizzate con null ed altre che non ammettono questo valore. Per dichiarare una variabile nullable sarà sufficiente aggiungere un ? subito dopo il tipo della stessa.

Un esempio:

val userName: String = null // errore di compilazione

la dichiarazione con assegnazione appena vista genererà un errore di compilazione in quanto si sta tentando di assegnare il valore null ad un campo String non-null. Per correggere l’errore dovremo invece dichiarare la variabile in questo modo:

val userName: String? = null // ok

La cosa interessante è il fatto che questa verifica avvenga in fase di compilazione, riducendo quasi totalmente la possibilità d’introdurre erroneamente null reference nel nostro codice.

Lavorare con i tipi nullable

Compresa la differenza tra le due tipologie… di tipi, c’interessa capire come lavorare con esse e in particolare con i tipi nullable.

Supponiamo ad esempio di aver dichiarato un membro nullable nella nostra classe e di voler recuperare il valore di una sua property (o di voler invocare un suo metodo):

val obj : MyType? = // ...
val propValue = obj.prop

L’accesso alla proprietà prop, tramite il classico operatore . funzionerebbe correttamente per un obj dichiarato come non-null, ma nel nostro caso genererà un errore di compilazione. Qualcosa di questo tipo:

Error:(x, y) Kotlin: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type MyType?

Che fare, dunque?

Dalla nostra esperienza con Java, sappiamo bene che è buona norma verificare prima di tutto se il nostro oggetto è una null reference, proprio per evitare una NullPointerException durante questo tipo di operazioni.

Anche in Kotlin abbiamo la possibilità di effettuare un classico null check tramite l’istruzione if:

val propValue = if (obj != null) obj.prop else "no value"

Come vedete, dopo il null check possiamo invocare prop come se fosse non-null (con il .), questo grazie allo smart cast eseguito dal compilatore.

Nota Lo smart cast da nullable a non-null funziona solo per oggetti immutabili.

Kotlin però permette di fare tutto ciò in modo ancora più espressivo grazie a due operatori formidabili!

Gli operatori Safe Call ed Elvis

Un approccio alternativo al classico null check ce lo suggerisce l’errore di compilazione visto precedentemente.

Per accedere una proprietà o invocare un metodo di un campo potenzialmente nullo possiamo infatti sfruttare l’operatore ?. (safe call operator):

val propValue = obj?.prop

Molto semplicemente, questa espressione sta a significare: restituisci il valore di prop se obj è diverso da null, altrimenti restituisci proprio null.

Allo stesso modo, possiamo sfruttare l’operatore safe call anche per invocare metodi:

obj?.prop?.method()

In particolare, in questo caso il metodo verrà eseguito solamente se nessuno degli oggetti/proprietà nella catena d’invocazioni è uguale a null.

Come visto, quindi, l’operatore ?. permette di recuperare in modo sicuro il valore di una proprietà o, quando questo non fosse possibile, di ottenere un null (invece di una fastidiosa eccezione…).

Ma se volessimo fornire un valore alternativo, non nullo?

In tal caso entra in gioco l’operatore Elvis (?:), col suo mitico ciuffo! 😃

Analogamente a quanto visto con l’approccio if/else, possiamo scrivere qualcosa di simile:

val propValue = obj?.prop ?: "no value"

In questo modo, quando obj o prop risulteranno null, il valore "no value" verrà assegnato alla variabile. Fico, no?

L’operatore non-null assertion

Vediamo infine un’opzione che in generale andrebbe evitata in un linguaggio munito di null safety, ma che in alcuni casi potrebbe tornare utile.

Una delle poche situazioni, usando Kotlin, in cui ci ritroveremo faccia a faccia con una NullPointerException è in corrispondenza di espressioni in cui si fa uso dell’operatore !! (non-null assertion operator).

Applicando !! andiamo a forzare la conversione di un tipo nullable nel suo corrispondente non-null. In questo modo però ci esponiamo alla possibilità che venga sollevata un’eccezione in caso di null reference. Due esempi:

val i : Int? = 10
i!!.toFloat() // => 10.0

val j : Int? = null
j!!.toFloat() // => NullPointerException

Nel primo caso l’asserzione è corretta: la variabile i contiene un valore diverso da null, dunque l’invocazione del metodo toFloat va a buon fine. Nel secondo caso invece, avendo forzato la conversione di una variabile effettivamente valorizzata a null, otterremo la tanto odiata eccezione…

In conclusione

La null safety di Kotlin è uno strumento davvero utile, che ci tira fuori da un bel po’ di guai, ma che va usato con consapevolezza.

L’utilizzo idiomatico degli operatori ?. e ?: rende il tutto più snello e divertente, ma bisogna sempre far attenzione alla leggibilità del codice che, a mio parere, deve rimanere una delle priorità di ogni programmatore saggio.

A presto,

David