Tratamiento de null en Kotlin

video[Tutorial Tratamiento de null en Kotlin

Uno de los errores más comunes en programación es el odiado NullPointerException. Ocurre cuando el programador olvida inicializar algún objeto o este no ha podido hacerlo por algún tipo de problema. Para tratar de minimizar al máximo la aprición de este tipo de error Kotlin incorpora una serie de medidas, que vamos a tratar en este apartado.
Kotlin soporta la gestión de null de forma nativa, dándonos la posibilidad de definir si una variable puede ser nula o no. De este modo, el compilador puede detectar posibles errores del tipo NullPointerException en tiempo de compilación, reduciendo la posibilidad de que se produzcan en tiempo de ejecución. Por defecto, todas las variables en Kotlin son non-nullable. De este modo, si intentamos asignar un valor null a cualquier variable, el compilador lanzará un error:

var saludo: String = "Hola"
saludo = null // Error compila-ción 
var saludoNullable: String? = "Ho-la"
saludoNullable = null // Compila 
Como se muestra a la derecha, si queremos permitir que una variable pueda ser null, tendremos que definirla añadiendo ? a su tipo de datos.
Dado que los errores de tipo NullPointerException se producen cuando intentamos acceder a un método o una propiedad de una variable nula, Kotlin evitará dichos errores denegando la llamada a los métodos o el acceso a las propiedades afectas. Sin embargo, en determinadas ocasiones será necesario utilizar esos métodos o propiedades. Para ello, disponemos de diversas alternativas.

Añadiendo una comprobación previa

Podemos comprobar si una variable es nula antes de realizar una operación, de igual forma como hacíamos en Java.
val nombreNullable: String? = "Juan"
if (nobreNullable != null) {
    println("Hola, ${nombreNullable.toUpperCase()}.")
    println("Tu nombre tiene ${nombreNullable.length} caracteres.")
} else {
    println("Hola, invitado")
} 
Si intentamos acceder a una variable nullable sin esta verificación ocurre un error de compilación. Para que compile hemos de usar el código de la derecha.
val s: String? = "Hola"
print(s.length) // No compila 
if (s != null) {
   print(s.length) // compila
} 

Llamada segura mediante operador ?.

La comprobación anterior es sencilla, pero necesita de mucho código para realizarla. Kotlin nos ofrece el operador ?. de cara a reducir ese código, de modo que podemos tener la comprobación de null y la llamada al método en la mis-ma línea. De este modo, el siguiente código:
nombre?.toUpperCase()  
if (nombre != null)
     nombre.toUpperCase()
else null 
Que es equivalente al código de la derecha.
Este operador nos va a permitir múltiples comprobaciones simultáneas:
val ciudadActual: String? = usuario?.direccion?.ciudad 
En el ejemplo, ciudadActual será null si usuario, direccion o ciudad son null.

El operador Elvis ?:

El operador Elvis se utiliza para ofrecer un valor por defecto cuando la variable a validar contiene null. Por ejemplo, el siguiente código:
nombre ?: "desconocido"
if (nombre != null) nombre
else                "desconocido" 
Que es equivalente al código de la derecha.
Uno de los usos más comunes de este operador es devolver un valor por defecto distinto a null cuando un método o propiedad es null:
val nombreMayuscula = nombre?.toUpperCase() ?: "DESCONOCIDO"
val ciudadActual = usuario?.direccion?.ciudad ?: "desconocida" 
La complejidad de las expresiones a la izquierda del operador Elvis puede ser incluso mayor, como se muestra en el segundo ejemplo.
Además, podemos utilizar la parte derecha del operador para lanzar una excepción o devolver un valor. Esto nos será de gran utilidad cuando evalua-mos las precondiciones de una función.
val n = nombreNullable ?: throw IllegalArgumentException("Nombre es null") 
Si además de devolver un valor necesitamos ejecutar múltiples operaciones a la derecha del operador elvis, será necesario incluirlas en un bloque de tipo run.
val a = b ?: run {
    val valorSiBEsNull = c.obtenValor()
    almacena(valorSiBEsNull)
    valorSiBEsNull
} 

El operador !!

Mediante el operador !! podemos saltarnos la comprobación de nulidad de una variable, de modo que se lanzará una excepción del tipo NullPointerException en caso que de la variable sea nula.
val nombre: String? = null
nombre!!.toUpperCase() // Produce un NullPointerException 
Se utiliza en aquellas ocasiones en que nosotros como programadores es-tamos seguros de que una variable no va a ser nula, aun pudiendo contener dicho valor, y nos reafirmamos ante el compilador. Es bastante común su apari-ción cuando migramos código de Java a Kotlin, pero dado que su filosofía es contraria a lo que intentamos conseguir con Kotlin, debemos evitar su utiliza-ción en la medida de lo posible. Un poco más adelante se describen varias alternativas para eliminar este operador en caso de una migración desde Java.

Interoperabilidad con Java

Kotlin es completamente interoperable con Java, pero Java no permite la nuli-dad como un tipo. Entonces ¿qué pasa cuando llamamos código Java desde Kotlin? En este caso, los tipos de Java, a los que llamaremos Platform Types, se tratan de un modo especial en Kotlin, el cual relajará las comprobaciones de nulidad en tiempo de compilación. Esto implica que toda la responsabilidad de las operaciones que realizamos con dichos datos recae en nosotros como pro-gramadores, por lo que deberemos tomar las medidas oportunas para evitar errores del tipo NullPointerException.
Por ejemplo, si tenemos la siguiente clase en Java:
public class Usuario {
    private final String nombre;
    public Usuario(String nombre) { this.nombre = nombre; }
    public String getNombre() { return nombre; }
} 
Kotlin no conocerá la nulidad de la variable nombre, por lo que permitirá todo tipo de operaciones sobre la misma. Por lo tanto, como el compilador no va aplicar ningún tipo de restricción, podremos tratarla como non-nullable:
val usuarioJava = Usuario(null)
println(usuarioJava.nombre.toUpperCase()) // Posible NullPointerException
println(usuarioJava.nombre.length)        // Posible NullPointerException 
o como nullable:
val usuarioJava = Usuario(null)
println(usuarioJava.nombre?.toUpperCase()) // Posible escritura de null
println(usuarioJava.nombre?.length)        // Posible escritura de null