Tipos que aceptan valores NULL y tipos no nulos

El sistema de tipos de Kotlin tiene como objetivo eliminar el peligro de null referencias del código, también conocido como El error del billón de dólares.

Uno de los errores más comunes en muchos lenguajes de programación, incluido Java, es que acceder a un miembro de un null referencia dará como resultado una null excepción de referencia. En Java, esto sería el equivalente a un NullPointerException o NPE para abreviar.

El sistema de tipos de Kotlin tiene como objetivo eliminar NullPointerExceptiones de nuestro código. Las únicas causas posibles de NPE pueden ser:

  • Una llamada explícita a throw NullPointerException();
  • Uso del !! operador que se describe a continuación;
  • Alguna inconsistencia de datos con respecto a la inicialización, como cuando:
    • Un no inicializado esta disponible en un constructor se pasa y se usa en algún lugar (“filtrando esta“);
    • Un constructor de superclase llama a un miembro abierto cuya implementación en la clase derivada usa un estado no inicializado;
  • Interoperación de Java:
    • Intenta acceder a un miembro en un null referencia de un tipo de plataforma;
    • Tipos genéricos utilizados para la interoperación de Java con nulabilidad incorrecta, por ejemplo, un fragmento de código Java podría agregar null en un Kotlin MutableList, significa que MutableList debe usarse para trabajar con él;
    • Otros problemas causados ​​por el código Java externo.

En Kotlin, el sistema de tipos distingue entre referencias que pueden contener null (referencias que aceptan valores NULL) y las que no pueden (nonull referencias). Por ejemplo, una variable regular de tipo String no puede sostener null:

funmain()//sampleStartvar a: String ="abc"// Regular initialization means non-null by default
    a =null// compilation error//sampleEnd

Para permitir valores nulos, podemos declarar una variable como anulable string, escrito String?:

funmain()//sampleStartvar b: String?="abc"// can be set null
    b =null// okprint(b)//sampleEnd

Ahora, si llama a un método o accede a una propiedad en a, se garantiza que no provocará una NPE, por lo que puede decir con seguridad:

val l = a.length

Pero si desea acceder a la misma propiedad en b, eso no sería seguro y el compilador informa un error:

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

Pero todavía necesitamos acceder a esa propiedad, ¿verdad? Hay varias formas de hacerlo.

Verificando para null en condiciones

Primero, puede verificar explícitamente si b es nully maneje las dos opciones por separado:

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

El compilador rastrea la información sobre la verificación que realizó y permite que la llamada length dentro de si. También se admiten condiciones más complejas:

funmain()//sampleStartval b: String?="Kotlin"if(b !=null&& b.length >0)print("String of length $b.length")elseprint("Empty string")//sampleEnd

Tenga en cuenta que esto solo funciona donde b es inmutable (es decir, una variable local que no se modifica entre la comprobación y el uso o un miembro val que tiene un campo de respaldo y no es reemplazable), porque de lo contrario podría suceder que b cambios a null después del cheque.

Llamadas seguras

Su segunda opción es el operador de llamada segura, escrito ?.:

funmain()//sampleStartval a ="Kotlin"val b: String?=nullprintln(b?.length)println(a?.length)// Unnecessary safe call//sampleEnd

Esto vuelve b.length si b no es null, y null de lo contrario. El tipo de esta expresión es Int?.

Las llamadas seguras son útiles en cadenas. Por ejemplo, si Bob, un empleado, puede ser asignado a un departamento (o no), que a su vez puede tener otro empleado como jefe de departamento, entonces para obtener el nombre del jefe de departamento de Bob (si lo hay), escribimos lo siguiente :

bob?.department?.head?.name

Vuelve una cadena así null si alguna de las propiedades que contiene es null.

Para realizar una determinada operación solo paranull valores, puede utilizar el operador de llamada segura junto con let:

funmain()//sampleStartval listWithNulls: List<String?>=listOf("Kotlin",null)for(item in listWithNulls)
         item?.letprintln(it)// prints Kotlin and ignores null//sampleEnd

También se puede colocar una llamada segura en el lado izquierdo de una tarea. Entonces, si uno de los receptores de la cadena de llamadas seguras está null, se omite la asignación y la expresión de la derecha no se evalúa en absoluto:

// If either `person` or `person.department` is null, the function is not called:
person?.department?.head = managersPool.getManager()

Operador Elvis

Cuando tenemos una referencia anulable b, podemos decir “si b no es null, úselo; de lo contrario, utilice algunosnull valor”:

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

Junto con el completo si-expresión, esto se puede expresar con el operador de Elvis, escrito ?::

val l = b?.length ?:-1

Si la expresión a la izquierda de ?: no es null, el operador de elvis lo devuelve; de ​​lo contrario, devuelve la expresión a la derecha. Tenga en cuenta que la expresión del lado derecho se evalúa solo si el lado izquierdo es null.

Tenga en cuenta que, dado que lanzar y regreso son expresiones en Kotlin, también se pueden usar en el lado derecho del operador elvis. Esto puede resultar muy útil, por ejemplo, para comprobar los argumentos de las funciones:

funfoo(node: Node): String?val parent = node.getParent()?:returnnullval name = node.getName()?:throwIllegalArgumentException("name expected")// ...

los !! Operador

La tercera opción es para los amantes de la NPE: la nonull operador de aserción!!) convierte cualquier valor en un valor nonull type y lanza una excepción si el valor es null. Podemos escribir b!!, y esto devolverá un nonull valor de b (por ejemplo, un String en nuestro ejemplo) o lanzar una NPE si b es null:

val l = b!!.length

Por lo tanto, si desea un NPE, puede tenerlo, pero debe solicitarlo explícitamente y no aparece de la nada.

Casts seguros

Los yesos regulares pueden resultar en ClassCastException si el objeto no es del tipo de destino. Otra opción es usar yesos seguros que regresen null si el intento no tuvo éxito:

val aInt: Int?= a as? Int

Colecciones de tipos que aceptan valores NULL

Si tiene una colección de elementos de un tipo que acepta valores NULL y desea filtrarnull elementos, puede hacerlo utilizando filterNotNull:

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