43 - Valores nulos en variables

Hasta ahora no analizamos como Kotlin trata los valores nulos (null) en la definición de variables.

En Kotlin se trata en forma separada las variables que permiten almacenar un valor null y aquellas que por su naturaleza no lo pueden almacenar.

Hemos trabajado hasta ahora con variables que no pueden almacenar un valor null, fácilmente lo podemos comprobar con las siguientes líneas de código:

null variables Kotlin error

La variable nombre debe almacenar una cadena de caracteres y por definición no puede almacenar un valor null.

Podemos perfectamente asignarle otro objeto de tipo String, pero no el valor null:

    var nombre: String
    nombre = "Juan"
    nombre = "Ana"

Lo mismo ocurre si definimos variables de tipo Int, Float, IntArray etc:

    var telefono: Int
    telefono = null // error de compilación 
    var arreglo: IntArray = IntArray(5)
    arreglo = null // error de compilación

Para permitir que una variable contenga un valor null, debemos agregar el caracter "?" en el momento que definimos el tipo de la variable (con esto indicamos al compilador de Kotlin que son variables que permiten almacenar el valor null):

fun main(args: Array<String>) {
    var telefono: Int?
    telefono = null
    var arreglo: IntArray? = IntArray(5)
    arreglo = null
}

Cuando trabajamos con variables que pueden almacenar valores nulos nuestro código debe verificar el valor que almacena la variable:

fun main(args: Array<String>) {
    var nombre: String
    print("Ingrese su nombre:")
    nombre = readln()
    println("El nombre ingresado es $nombre")}

Hasta ahora para hacer más simples nuestros programas cuando cargábamos datos por teclado llamábamos a la función readLine() y el operador !! al final,esto debido a que la función readLine retorna un tipo de dato String?:

public fun readLine(): String? = stdin.readLine()

Si la función readLine retorna un String? no lo podemos almacenar en una variable String. Para poder copiar un dato que puede almacenar un valor null en otro que no lo puede hacer utilizamos el !! y debe evitarse en lo posible.

Podemos ahora modificar el programa anterior y definir una variable String?:

fun main(args: Array<String>) {
    var nombre: String?
    print("Ingrese su nombre:")
    nombre = readLine()
    println("El nombre ingresado es $nombre")
}
Como vemos ahora no disponemos el operador !! al final de la llamada a readLine.

Control de nulos

Cuando trabajamos con variables que pueden almacenar valor nulo podemos mediante if verificar si su valor es distinto a null.

fun main(args: Array<String>) {
    var cadena1: String? = null
    var cadena2: String? = "Hola"
    if (cadena1 != null)
        println("cadena1 almacena $cadena1")
    else
        println("cadena1 apunta a null")
    if (cadena2 != null)
        println("cadena2 almacena $cadena2")
    else
        println("cadena2 apunta a null")
}

Mediante if verificamos si la variable almacena un null o un valor distinto a null y actuamos según el valor almacenado:

    if (cadena1 != null)
        println("cadena1 almacena $cadena1")
    else
        println("cadena1 apunta a null")

Podemos intentar llamar a sus métodos y propiedades sin que se genere un error cuando disponemos el operador "?" seguido a la variable:

    var cadena1: String? = null
    println(cadena1?.length)

Se imprime en pantalla un null pero no se genera un error ya que no se accede a la propiedad length de la clase String, esto debido a que cadena1 almacena un null.

Esta sintaxis es muy conveniente si tenemos llamadas como:

if (empleado1?.datosPersonales?.telefono? != null)

Cualquiera de los objetos que apunte a null luego el if se verifica falso.

Un ejemplo de acceso sería:

data class DatosPersonales(val nombre: String, val telefono: Int?)
data class Empleado (val nroEmpleado: Int, val datosPersonales: DatosPersonales?)

fun main(args: Array<String>) {
    var empleado1: Empleado?
    empleado1= Empleado(100, DatosPersonales("Juan", null))
    if (empleado1?.datosPersonales?.telefono == null )
        println("El empleado no tiene telefono")
    else
        println("El telefono del empleado es ${empleado1?.datosPersonales?.telefono}")
}

Con esta sintaxis decimos que la variable empleado1 puede almacenar null (por ejemplo si la empresa no tiene empleados):

    var empleado1: Empleado?

La propiedad datosPersonales de la clase Empleado puede almacenar null (por ejemplo si no tenemos los datos personales del empleado):

data class Empleado (val nroEmpleado: Int, val datosPersonales: DatosPersonales?)

La clase DatosPersonales tiene una propiedad telefono que puede almacenar null (por ejemplo si el empleado no tiene teléfono):

data class DatosPersonales(val nombre: String, val telefono: Int?)

Para imprimir el teléfono de un empleado debemos controlar si existe el empleado, si tiene almacenado sus datos personales y si tiene teléfono, una aproximación con varios if sería:

    if (empleado1 != null)
        if (empleado1.datosPersonales != null)
            if (empleado1.datosPersonales?.telefono != null)
                println("El telefono del empleado es ${empleado1.datosPersonales?.telefono}")

Analizando si tienen almacenado valores distintos a null hasta llegar a la propiedad del telefono para mostrarla.

Pero en Kotlin la sintaxis más concisa para acceder al teléfono es:

    if (empleado1?.datosPersonales?.telefono == null )
        println("El empleado no tiene telefono")
    else
        println("El telefono del empleado es ${empleado1?.datosPersonales?.telefono}")

Ya sea que empleado1 almacene null o la propiedad datosPersonales almacene null luego retorna null la expresión.