Usa la nulabilidad en Kotlin

1. Antes de comenzar

En este codelab, aprenderás sobre la nulabilidad y la importancia de la seguridad null. La nulabilidad es un concepto que se suele encontrar en muchos lenguajes de programación. Se refiere a la capacidad de las variables de tener una ausencia de valor. En Kotlin, la nulabilidad se trata de forma intencional para lograr la seguridad de null.

Requisitos previos

  • Conocimiento de los conceptos básicos de programación de Kotlin, incluidas las variables, el acceso a métodos y las propiedades desde una variable y las funciones println() y main()
  • Conocimientos de condicionales de Kotlin, incluidas las sentencias if/else y las expresiones booleanas

Qué aprenderás

  • ¿Qué es null?
  • La diferencia entre los tipos que admiten valores anulables y no anulables
  • Qué es la seguridad de null, su importancia y cómo Kotlin logra la seguridad de null
  • Cómo acceder a los métodos y las propiedades de las variables anulables con el operador de llamada segura ?. y el operador de aserción no nulo !!
  • Cómo realizar comprobaciones de null con condicionales if/else
  • Cómo convertir una variable anulable en un tipo no anulable con expresiones if/else
  • Cómo proporcionar un valor predeterminado cuando una variable anulable es null con la expresión if/else o el operador Elvis ?:

Requisitos

  • Un navegador web con acceso a Playground de Kotlin

2. Usa variables anulables

¿Qué es null?

En la Unidad 1, aprendiste que, cuando declaras una variable, debes asignarle un valor de inmediato. Por ejemplo, cuando declaras una variable favoriteActor, puedes asignarle inmediatamente un valor de string "Sandra Oh".

val favoriteActor = "Sandra Oh"

Cuadro que representa una variable de favoriteActor a la que se le asignó un valor de string "Sandra Oh".

Sin embargo, ¿qué pasa si no tienes un actor favorito? Te recomendamos que asignes un valor "Nobody" o "None" a la variable. Este no es un buen enfoque porque tu programa interpreta que la variable favoriteActor tiene un valor "Nobody" o "None" en lugar de no mostrar ningún valor. En Kotlin, puedes usar null para indicar que no hay un valor asociado a la variable.

Un cuadro que representa una variable afavoriteActor a la que se le asigna un valor nulo.

Para usar null en el código, sigue estos pasos:

  1. En el Playground de Kotlin, reemplaza el contenido en el cuerpo de la función main() por una variable favoriteActor establecida en null:
fun main() {
    val favoriteActor = null
}
  1. Imprime el valor de la variable favoriteActor con la función println() y ejecuta este programa:
fun main() {
    val favoriteActor = null
    println(favoriteActor)
}

El resultado se ve como este fragmento de código:

null

Reasignaciones de variables con null

Anteriormente, aprendiste que puedes reasignar variables definidas con la palabra clave var a diferentes valores del mismo tipo. Por ejemplo, puedes volver a asignar una variable name declarada con un nombre a otro, siempre que el nuevo nombre sea del tipo String.

var favoriteActor: String = "Sandra Oh"
favoriteActor = "Meryl Streep"

Hay ocasiones en las que puedes declarar una variable cuando quieras asignarla a null. Por ejemplo, después de declarar a tu actor favorito, no decides revelarlo. En este caso, es útil asignar la variable favoriteActor a null.

Información sobre variables anulables y no anulables

Para reasignar la variable favoriteActor a null, sigue estos pasos:

  1. Cambia la palabra clave val por var y, luego, especifica que la variable favoriteActor sea un tipo String y asígnala al nombre de tu actor favorito:
fun main() {
    var favoriteActor: String = "Sandra Oh"
    println(favoriteActor)
}
  1. Quita la función println():
fun main() {
    var favoriteActor: String = "Sandra Oh"
}
  1. Reasigna la variable favoriteActor a null y ejecuta este programa:
fun main() {
    var favoriteActor: String = "Sandra Oh"
    favoriteActor = null
}

Recibirás el siguiente mensaje de error:

Un mensaje de advertencia que dice: "El valor nulo no puede ser de una string de tipo no nulo".

En Kotlin, hay una distinción entre tipos anulables y no anulables:

  • Los tipos anulables son variables que pueden contener null.
  • Los tipos no nulos son variables que no pueden contener null.

Un tipo solo es anulable si permites explícitamente que retenga null. Como se indica en el mensaje de error, el tipo de datos String es no anulable, por lo que no puedes reasignar la variable a null.

Un diagrama que muestra cómo declarar variables de tipo anulables. Comienza con una palabra clave var seguida del nombre del bloque de variable, un punto y coma, el tipo de variable, un signo de interrogación, el signo igual y el bloque de valor.  El bloque de tipo y el signo de interrogación se denotan con un texto de tipo anulable que indica que el tipo seguido de signo de interrogación es lo que lo convierte en un tipo anulable.

Para declarar variables anulables en Kotlin, debes agregar un operador ? al final del tipo. Por ejemplo, un tipo String? puede contener una string o null, mientras que un tipo String solo puede contener una string. Para declarar una variable anulable, debes agregar explícitamente el tipo anulable. Sin el tipo anulable, el compilador de Kotlin infiere que es un tipo no anulable.

  1. Cambia el tipo de variable favoriteActor de un tipo de datos String a un tipo String?:
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    favoriteActor = null
}
  1. Imprime la variable favoriteActor antes y después de la reasignación null y, luego, ejecuta este programa:
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor)

    favoriteActor = null
    println(favoriteActor)
}

El resultado se ve como este fragmento de código:

Sandra Oh
null

La variable favoriteActor contenía originalmente una string y, luego, se convirtió en null.

Pruébalo

Ahora que puedes usar el tipo anulable String?, ¿puedes inicializar una variable con un valor Int y reasignarla a null?

Escribe un valor Int anulable

  1. Quita todo el código de la función main():
fun main() {

}
  1. Crea una variable number de un tipo Int anulable y asígnale un valor 10:
fun main() {
    var number: Int? = 10
}
  1. Imprime la variable number y ejecuta este programa:
fun main() {
    var number: Int? = 10
    println(number)
}

El resultado es el esperado:

10
  1. Reasigna la variable number a null para confirmar que la variable sea anulable:
fun main() {
    var number: Int? = 10
    println(number)

    number = null
}
  1. Agrega otra sentencia println(number) como línea final del programa y, luego, ejecútalo:
fun main() {
    var number: Int? = 10
    println(number)

    number = null
    println(number)
}

El resultado es el esperado:

10
null

3. Procesa variables anulables

Anteriormente, aprendiste a usar el operador . para acceder a propiedades y métodos de variables no anulables. En esta sección, aprenderás a usarlo para acceder a métodos y propiedades de variables anulables.

Para acceder a una propiedad de la variable favoriteActor no anulable, sigue estos pasos:

  1. Quita todo el código de la función main() y, luego, declara una variable favoriteActor de tipo String y asígnala al nombre de tu actor favorito:
fun main() {
    var favoriteActor: String = "Sandra Oh"
}
  1. Imprime la cantidad de caracteres en el valor de la variable favoriteActor con la propiedad length y, luego, ejecuta este programa:
fun main() {
    var favoriteActor: String = "Sandra Oh"
    println(favoriteActor.length)
}

El resultado es el esperado:

9

Hay nueve caracteres en el valor de la variable favoriteActor, incluidos los espacios. Es posible que la cantidad de caracteres del nombre de tu actor favorito sea diferente.

Cómo acceder a una propiedad de una variable anulable

Imagina que deseas que la variable favoriteActor sea anulable, de manera que las personas que no tengan un actor favorito puedan asignarla a null.

Para acceder a una propiedad de la variable favoriteActor anulable, sigue estos pasos:

  • Cambia el tipo de variable favoriteActor a uno anulable y, luego, ejecuta este programa:
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor.length)
}

Recibirás el siguiente mensaje de error:

Un mensaje de error que dice: "¿Solo se permiten llamadas seguras o no nulas en un receptor anulable de tipo String".

Este es un error de compilación. Como se mencionó en un codelab anterior, un error de compilación se produce cuando Kotlin no puede compilar el código debido a un error de sintaxis en tu código.

Kotlin aplica reglas sintácticas de forma intencional para lograr la seguridad de null, que hace referencia a una garantía de que no se realizarán llamadas accidentales en variables null. Esto no significa que las variables no pueden ser null. Significa que, si se accede a un miembro de una variable, la variable no puede ser null.

Esto es fundamental porque, si se intenta acceder a un miembro de una variable null ( conocido referencia null) durante la ejecución de una app, esta fallará porque la variable null no contiene ninguna propiedad ni método. Este tipo de falla se conoce como error de tiempo de ejecución, ya que el error ocurre después de que se compila y se ejecuta el código.

Debido a la naturaleza de seguridad de null de Kotlin, se evitan esos errores de tiempo de ejecución porque el compilador de Kotlin fuerza una verificación de null para tipos anulables. La verificación Null hace referencia a un proceso en el que se verifica si una variable podría ser null antes de que se acceda a ella y se la trate como un tipo no anulable. Si deseas usar un valor anulable como su tipo no anulable, debes realizar una verificación de null de forma explícita. Obtén más información al respecto en la sección Cómo usarcondicionales if/else que se encuentra más adelante en este codelab.

En este ejemplo, el código falla en el tiempo de compilación porque no se permite la referencia directa a la propiedad length para la variable favoriteActor debido a que existe la posibilidad de que la variable sea null.

A continuación, aprenderás diversas técnicas y operadores para trabajar con tipos anulables.

Usa el operador de llamada segura ?..

Puedes usar el operador de llamada segura ?. para acceder a métodos o propiedades de variables anulables.

Un diagrama que muestra un bloque de variable anulable seguido de un signo de interrogación, un punto y un bloque de método o propiedad. No hay espacios intermedios.

Para usar el operador de llamada segura ?. a fin de acceder a un método o una propiedad, agrega un símbolo ? después del nombre de la variable y accede al método o a la propiedad con la notación ..

El operador de llamada segura ?. permite un acceso más seguro a las variables anulables, ya que el compilador de Kotlin detiene cualquier intento de acceso del miembro a las referencias null y muestra null para el miembro al que se accede.

Para acceder de manera segura a una propiedad de la variable favoriteActor anulable, sigue estos pasos:

  1. En la sentencia println(), reemplaza el operador . por el operador de llamada segura ?.:
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor?.length)
}
  1. Ejecuta este programa y, luego, verifica que el resultado sea el esperado:
9

Es posible que la cantidad de caracteres del nombre de tu actor favorito sea diferente.

  1. Reasigna la variable favoriteActor a null y ejecuta este programa:
fun main() {
    var favoriteActor: String? = null
    println(favoriteActor?.length)
}

Verás el siguiente resultado:

null

Ten en cuenta que el programa no falla incluso en caso de intento de acceso a la propiedad length de una variable null. La expresión de llamada segura simplemente muestra null.

Usa el operador de aserción no nulo de !!

También puedes usar el operador de aserción !! no nulo para acceder a métodos o propiedades de variables anulables.

Un diagrama que muestra un bloque de variable anulable seguido de dos signos de exclamación, un solo punto y un bloque de método o propiedad. No hay espacios intermedios.

Después de la variable anulable, debes agregar el operador de aserción !! no nulo seguido del operador . y, luego, el método o la propiedad sin espacios.

Como sugiere el nombre, si usas la aserción no nula !!, significa que confirmas que el valor de la variable no es null, sin importar si lo es o no.

A diferencia de los operadores de llamada segura ?., el uso de un operador de aserción no nulo de !! puede generar un error NullPointerException si la variable anulable es realmente null. Por lo tanto, debe hacerse solo cuando la variable siempre es no anulable o si se estableció un adecuado manejo de excepción. Si no se controlan, las excepciones causan errores de tiempo de ejecución. Aprenderás sobre el manejo de excepciones en unidades posteriores de este curso.

Para acceder a una propiedad de la variable favoriteActor con el operador de aserción no nulo de !!, sigue estos pasos:

  1. Reasigna la variable favoriteActor al nombre de tu actor favorito y, luego, reemplaza el operador de llamada segura ?. por el operador de aserción no nulo !! en la sentencia println():
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor!!.length)
}
  1. Ejecuta este programa y, luego, verifica que el resultado sea el esperado:
9

Es posible que la cantidad de caracteres del nombre de tu actor favorito sea diferente.

  1. Reasigna la variable favoriteActor a null y ejecuta este programa:
fun main() {
    var favoriteActor: String? = null
    println(favoriteActor!!.length)
}

Recibes un error NullPointerException:

Un mensaje de error que dice: "Excepción en el subproceso 'principal' java.lang.NullPointerException".

Este error de Kotlin muestra que tu programa falló durante la ejecución. Por lo tanto, no se recomienda usar el operador de aserción no nulo !!, a menos que sepas con seguridad que la variable no es null.

Usa los condicionales if/else

Puedes usar la rama if en los condicionales if/else para realizar verificaciones null.

Un diagrama que muestra un bloque de variable anulable seguido de un signo de exclamación, un signo igual y un valor nulo.

Para realizar verificaciones de null, puedes revisar que la variable anulable no sea igual a null con el operador de comparación !=.

Sentencias if/else

Se puede usar una sentencia if/else junto con una verificación null de la siguiente manera:

Un diagrama que describe una sentencia if/else con la palabra clave if seguida de paréntesis con un bloque de verificación nulo en su interior, un par de llaves con body 1 (cuerpo 1) dentro, una palabra clave else y otro par de llaves con body 2 (cuerpo 2) en su interior. La cláusula else está encapsulada en un cuadro rojo punteado, que se indica como opcional.

La verificación de null es útil cuando se combina con una sentencia if/else:

  • La verificación de null de la expresión nullableVariable != null se usa como la condición if.
  • Body 1 dentro de la rama if supone que la variable no es anulable. Por lo tanto, en este cuerpo, puedes acceder libremente a los métodos o las propiedades de la variable como si fuera no anulable sin usar un operador de llamada segura ?. o un operador de aserción no nulo de !!.
  • El body 2 dentro de la rama else supone que la variable es null. Por lo tanto, en este cuerpo, puedes agregar sentencias que deberían ejecutarse cuando la variable es null. La rama else es opcional. Solo puedes usar el condicional if para ejecutar una verificación de null sin proporcionar una acción predeterminada cuando falla la verificación de null.

La verificación de null es más conveniente para usar con la condición if cuando hay varias líneas de código que usan la variable anulable. Por el contrario, el operador de llamada segura ?. es más conveniente para una sola referencia de la variable anulable.

A fin de escribir una sentencia if/else con una verificación de null para la variable favoriteActor, sigue estos pasos:

  1. Vuelve a asignar la variable favoriteActor al nombre de tu actor favorito y, luego, quita la sentencia println():
fun main() {
    var favoriteActor: String? = "Sandra Oh"

}
  1. Agrega una rama if con una condición favoriteActor != null:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {

    }
}
  1. En el cuerpo de la rama if, agrega una sentencia println que acepte una string "The number of characters in your favorite actor's name is ${favoriteActor.length}" y, luego, ejecuta este programa:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    }
}

El resultado es el esperado.

The number of characters in your favorite actor's name is 9.

Es posible que la cantidad de caracteres del nombre de tu actor favorito sea diferente.

Ten en cuenta que puedes acceder al método de longitud del nombre directamente con el operador . porque accedes al método length dentro de la rama if después de la verificación de null. Como tal, el compilador de Kotlin sabe que no hay posibilidad de que la variable favoriteActor sea null, por lo que permite el acceso directo a la propiedad.

  1. Opcional: Agrega una rama else para controlar una situación en la que el nombre del actor sea null:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {

    }
}
  1. En el cuerpo de la rama else, agrega una sentencia println que tome una string "You didn't input a name.":
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}
  1. Asigna la variable favoriteActor a null y ejecuta este programa:
fun main() {
    var favoriteActor: String? = null

    if(favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}

El resultado es el esperado:

You didn't input a name.

Expresiones if/else

También puedes combinar la verificación de null con una expresión if/else a fin de convertir una variable anulable en una variable no anulable.

Un diagrama que describe una expresión if/else con la palabra clave val seguida de un bloque de nombre, dos puntos y un bloque de tipo no nulo, un símbolo igual, la palabra clave if, paréntesis con una condición dentro, un par de llaves con body 1 (cuerpo 1) dentro de ellas, una palabra clave else con otro par de llaves y un bloque 2 (cuerpo 2) en su interior.

Para asignar una expresión if/else a un tipo no anulable, sigue estos pasos:

  • La verificación de null nullableVariable != null se usa como la condición if.
  • Body 1 dentro de la rama if supone que la variable no es anulable. Por lo tanto, en este body, puedes acceder a métodos o propiedades de la variable como si fuera una variable no anulable sin un operador seguro ?. ni un operador de aserción no nulo !!.
  • El body 2 dentro de la rama else supone que la variable es null. Por lo tanto, en este body, puedes agregar sentencias que deberían ejecutarse cuando la variable es null.
  • En la línea final de body 1 y body 2, debes usar una expresión o un valor que de como resultado un tipo no anulable para que se asigne a la variable no anulable cuando la verificación de null sea exitosa o falle, respectivamente.

Si deseas usar la expresión if/else para reescribir el programa a fin de que solo use una sentencia println, sigue estos pasos:

  1. Asigna la variable favoriteActor al nombre de tu actor favorito:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}
  1. Crea una variable lengthOfName y asígnala a la expresión if/else:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}
  1. Quita ambas sentencias println() de las ramas if y else:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {

    } else {

    }
}
  1. En el cuerpo de la rama if, agrega una expresión favoriteActor.length:
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {
      favoriteActor.length
    } else {

    }
}

Se puede acceder a la propiedad length de la variable favoriteActor directamente con el operador ..

  1. En el cuerpo de la rama else, agrega un valor 0:
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {
      favoriteActor.length
    } else {
      0
    }
}

El valor 0 actúa como predeterminado cuando el nombre es null.

  1. Al final de la función main(), agrega una sentencia println que tome una string "The number of characters in your favorite actor's name is $lengthOfName." y ejecuta este programa:
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {
      favoriteActor.length
    } else {
      0
    }

    println("The number of characters in your favorite actor's name is $lengthOfName.")
}

El resultado es el esperado:

The number of characters in your favorite actor's name is 9.

Puede haber diferencias entre la cantidad de caracteres del nombre que usaste.

Usa el operador Elvis ?:

El operador Elvis ?: se puede utilizar junto con el operador de llamada segura ?.. Con el operador Elvis ?:, puedes agregar un valor predeterminado cuando el operador de llamada segura ?. muestre null. Es similar a una expresión if/else, pero más idiomática.

Si la variable no es null, se ejecuta la expresión antes que el operador Elvis ?:. Si la variable es null, se ejecuta la expresión después del operador Elvis ?:.

Un diagrama que muestra la palabra clave val, seguida de un bloque de nombre, un signo igual, un bloque de variable anulable, un signo de interrogación, un punto, un método o bloque de propiedad, un signo de interrogación, dos puntos y un bloque de valor predeterminado.

Para modificar el programa anterior a fin de usar el operador Elvis ?:, sigue estos pasos:

  1. Quita el condicional if/else, establece la variable lengthOfName en la variable favoriteActor anulable y usa el operador de llamada segura ?. para llamar a su propiedad length:
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = favoriteActor?.length

    println("The number of characters in your favorite actor's name is $lengthOfName.")
}
  1. Después de la propiedad length, agrega el operador Elvis ?: seguido de un valor 0 y ejecuta este programa:
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = favoriteActor?.length ?: 0

    println("The number of characters in your favorite actor's name is $lengthOfName.")
}

El resultado es el mismo que el del resultado anterior:

The number of characters in your favorite actor's name is 9.

4. Conclusión

¡Felicitaciones! Aprendiste sobre la nulabilidad y cómo usar varios operadores para administrarla.

Resumen

  • Se puede establecer una variable en null para indicar que no tiene ningún valor.
  • No se pueden asignar variables no anulables null.
  • Se pueden asignar variables anulables null.
  • Para acceder a los métodos o las propiedades de las variables anulables, debes usar operadores de llamada segura ?. u operadores de aserción no nulos !!.
  • Puedes usar sentencias if/else con verificaciones de null para acceder a variables anulables en contextos no anulables.
  • Puedes convertir una variable anulable en un tipo no anulable con expresiones if/else.
  • Puedes proporcionar un valor predeterminado cuando una variable anulable sea null con la expresión if/else o el operador Elvis ?:.

Más información