Null Safety e Riferimenti Nullabili e non Nullabili

Kotlin a differenza di Java prevede che tutti i tipi sono degli oggetti (ha eliminato i tipi primitivi e le classi wrapper), e quindi passati per riferimento. Ma proprio per questa mancanza deve migliorare il sistema per evitare le NullPointerException (NPE), in quanto basterebbe che uno si dimentichi di inizializzare una variabile e scatta la NPE.

Infatti per essere all'altezza di altri linguaggi di programmazione dove esistono i tipi passati per valore non nullabili (vedi i tipi derivanti dalle struct del C# o Swift) e per by-passare le limitazioni della JVM (essa non permette la definizione di tipi di valore) Kotlin fa una distinzione tra i riferimenti che possono contenere null (riferimenti nullabili) e quelli che non possono (riferimenti non nullabili). Ad esempio, una variabile di tipo, se non esplicitamente detto tramite l'operatore ? descritto di seguito , String non può contenere null:

Secondo la documentazione ufficiale di Kotlin ci sono pochi casi in cui si può generare una NullPointerException:

  • Una chiamata esplicita a throw NullPointerException();
  • Uso dell'operatore !!, descritto di seguito, che genera una KotlinNullPointerException;
  • Del codice Java esterno;
  • C'è qualche incongruenza tra i tipi di dati nell'inizializzazione delle variabili, quasi sempre segnalate dal compilatore.

Riferimento non nullabile:

Dichiarando una variabile e poi provando ad assegnarli il valore null:

var a: String = "abc"

a = null // compilation error

Come si nota non è possibile assegnare in nessun modo il riferimento nullo ad una variabile non nullabile. Il compilatore non te fa fare.

Riferimento nullabile e operatore ?:

Per consentire il valore null, possiamo dichiarare una variabile come stringa nullable, con la scritta String?:

var b: String? = "abc"

b = null // ok

Ora, se chiamate un metodo o accedi a una proprietà a (dello Snippet 10.16), è garantito che non provochi un NPE, in e possiamo tranquillamente fare:

val l = a.length

Ma se si desidera accedere alla stessa proprietà b (dello Snippet 10.17), ciò non sarebbe sicuro e il compilatore segnala un errore:

val l = b.length // error: variable 'b' can be null

Ma abbiamo ancora bisogno di accedere a quella proprietà, giusto? Ci sono alcuni modi per farlo.

Controllo anti-null

In primo luogo, è possibile controllare esplicitamente se bè null e gestire separatamente le due opzioni:

val l = if (b != null) b.length else -1

Il compilatore traccia le informazioni sul controllo eseguito e consente la chiamata lengthall'interno del if. Sono supportate anche condizioni più complesse:

if (b != null && b.length > 0) {

print("String of length ${b.length}")

} else {

print("Empty string")

}

Si noti che questo funziona solo dove b è immutabile, nel seno che non deve essere stata fatta nessuna modifica al riferimento di b tra il controllo (b != null) e l'accesso ad un membro.

Operatore di accesso sicuro (?.)

La seconda opzione è l'operatore di accesso sicuro, che prevede l'inserimento di un ? prima del .:

b?.length

Questo restituisce b.length se b non è null e altrimenti null. Il tipo di ritorno, naturalmente è un Int nullabile, cioè Int?.

L'operatore di accesso sicuro si può anche applicare più e più volte nella stessa riga:

var name = bob?.department?.head?.name

Tale catena restituisce null se almeno una delle proprietà è null, altrimenti restituisce il valore.

let{}, also{} e run{}

Per eseguire una determinata operazione solo per valori non null, è possibile utilizzare l'operatore di chiamata di sicurezza insieme a let{}:

val listWithNulls: List<String?> = listOf("A", null)

for (item in listWithNulls) {

item?.let { println(it) } // prints A and ignores null

}

Se vogliamo applicare un'operazione aggiuntiva oltre a let{}, ad esempio accedere ad ogni valore non null, possiamo usare il metodo also{} in catena al metodo let{}:

val firstName = "Tom"

val secondName = "Michael"

val names: List<String?> = listOf(firstName, null, secondName)

var res = listOf<String?>()

for (item in names) {

item?.let { res = res.plus(it); it }

?.also{it -> println("non nullable value: $it")}

}

Kotlin ha anche un metodo run{} per eseguire un'operazione in un riferimento nullabile. È molto simile a let, ma la lambda non accetta parametri, ma si può usare this per rimediare:

val firstName = "Tom"

val secondName = "Michael"

val names: List<String?> = listOf(firstName, null, secondName)

var res = listOf<String?>()

for (item in names) {

item?.run{res = res.plus(this)}

}

In questo caso la parola chiave this si riferisce alla variabile item, questa speciale caratteristica di Kotlin verrà vista in futuro.


Operatore Elvis o null coalescing (?:)

L'operatore ?: è detto operatore Elvis o di null coalescing, ed è molto utile in una serie di situazioni permettendo di scrivere codice più elegante e ristretto in Kotlin.

Quante volte vi è capitato di dover scrivere un blocco if per controllare se una variabile è null, ed agire di conseguenza scegliendo un valore di default da assegnare alla stessa variabile? Ecco un esempio:

var name : String

if(str != null) {

name = str

}

else {

name = "senza nome"

}

Probabilmente starete pensando: "beh c'è sempre l'operatore ternario!", non in Kotlin! Ma potremmo sempre usare when:

var name = when(str) {

null -> "senza nome"

else -> str

}

Ma l'operatore elvis o di null coalescing consente di essere ancora più concisi. Esso verifica se l'operando a sinistra è diverso da null, ed in tal caso restituisce il valore dell'operando sinistro stesso. In caso contrario invece restituisce l'operando destro:

var nome = str ?: "senza nome"

è possibile anche realizzare una catena di operatori elvis:

var name = firstStr ?: secondStr ?: "questo caso è proprio disperato!"

In Kotlin le keyword throw e return possono essere utilizzate anche sul lato destro dell'operatore elvis. Questo può essere molto utile, per esempio, per controllare argomenti di funzione:

fun foo(node: Node): String? {

val parent = node.getParent() ?: return null

val name = node.getName() ?: throw IllegalArgumentException("name expected")

// ...

}

L'Operatore (!!)

L'ultima delle opzioni prevista da Kotlin è per quelli che amano l'NPE. Possiamo scrivere b!!e questo restituirà un valore non nullo b (ad esempio, Stringnel nostro esempio) o lanciare una NPE se bè nullo:

val l = b!!.length

Quindi, se si desidera lanciare una NPE, da gestire altrove, è possibile farlo ma necessità di una "dichiarazione esplicita" della volontà del programmatore.

Cast in sicurezza

I cast regolari, visti in una precedente lezione, possono risultare in un ClassCastException se l'oggetto non è del tipo di destinazione. Un'altra opzione è quella di utilizzare i cast di sicurezza che restituiscono null se il tentativo non è riuscito:

val aInt: Int? = a as? Int

Collezioni Nullabili

Ci sono due modi per trasformare una collezione nullabile in una non nullabile:

fun needList(list: List<Int>?) {

val notNullList: List<Int> = list!! // metodo 1

val notNullList: List<Int> = list ?: emptyList() // metodo 2

}

In Kotlin non esiste un operatore per l'accesso sicuro agli elementi dell'array, in stile di: ?[]. In questo caso dovremmo accedere tramite la funzione ?.get() e/o ?.set().

Collezioni di Riferimenti Nullabili

Se si dispone di una raccolta di elementi di tipo nullable e si desidera filtrare elementi non nullo, è possibile farlo utilizzando filterNotNull().

val nullableList: List<Int?> = listOf(1, 2, null, 4)

val intList: List<Int> = nullableList.filterNotNull()

https://kotlinlang.org/docs/reference/

https://stackoverflow.com/documentation/kotlin/topics

https://medium.com/makingtuenti/infix-functions-in-kotlin-2db3d3142dd2

http://www.baeldung.com/category/kotlin/

results matching ""

    No results matching ""