En Kotlin, ¿cuál es la forma idiomática de tratar con valores que aceptan nulos, hacer reference o convertirlos?

Si tengo un tipo anulable Xyz? , Quiero referencerlo o convertirlo a un tipo Xyz no admite Xyz . ¿Cuál es la forma idiomática de hacerlo en Kotlin?

Por ejemplo, este código está en error:

 val something: Xyz? = createPossiblyNullXyz() something.foo() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Xyz?" 

Pero si marque nulo primero, está permitido, ¿por qué?

 val something: Xyz? = createPossiblyNullXyz() if (something != null) { something.foo() } 

¿Cómo cambio o trato un valor como no null sin requerir la verificación if , suponiendo que estoy seguro de que nunca es null ? Por ejemplo, aquí estoy recuperando un valor de un map que puedo garantizar que existe y el resultado de get() no es null . Pero tengo un error:

 val map = mapOf("a" to 65,"b" to 66,"c" to 67) val something = map.get("a") something.toLong() // Error: "Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Int?" 

El método get() cree que es posible que el artículo se haya perdido y devuelve el tipo Int? . Por lo tanto, ¿cuál es la mejor manera de forzar que el tipo de valor no sea nulo?

Nota: esta pregunta fue escrita y respondida intencionalmente por el autor ( Preguntas de respuesta automática ), de modo que las respuestas idiomáticas a los temas de Kotlin comúnmente formulados están presentes en SO. También para aclarar algunas respuestas realmente antiguas escritas para alfas de Kotlin que no son precisas para Kotlin actual.

En primer lugar, debe leer todo sobre Seguridad Nula en Kotlin que cubre los casos a background.

En Kotlin, no se puede acceder a un valor que admite valores NULL sin estar seguro de que no es null ( Comprobando la nulidad de las condiciones ), o afirmando que seguramente no es null usando el !! seguro operador , accediendo con un ?. Llamada Segura , o por último, dando algo que posiblemente sea un valor pnetworkingeterminado null usando el operador ?: Elvis .

Para su primer caso en su pregunta tiene opciones dependiendo de la intención del código que usaría uno de estos, y todos son idiomáticos pero tienen diferentes resultados:

 val something: Xyz? = createPossiblyNullXyz() // access it as non-null asserting that with a sure call val result1 = something!!.foo() // access it only if it is not null using safe operator, // returning null otherwise val result2 = something?.foo() // access it only if it is not null using safe operator, // otherwise a default value using the elvis operator val result3 = something?.foo() ?: differentValue // null check it with `if` expression and then use the value, // similar to result3 but for more complex cases harder to do in one expression val result4 = if (something != null) { something.foo() } else { ... differentValue } // null check it with `if` statement doing a different action if (something != null) { something.foo() } else { someOtherAction() } 

Para el "Por qué funciona cuando null marcado", lea la información de background a continuación en smart cast .

Para su segundo caso en su pregunta en la pregunta con Map , si usted como desarrollador está seguro de que el resultado nunca será null , !! use !! seguro operador como una afirmación:

 val map = mapOf("a" to 65,"b" to 66,"c" to 67) val something = map.get("a")!! something.toLong() // now valid 

o en otro caso, cuando el map PODRÍA devolver un valor nulo, pero puede proporcionar un valor pnetworkingeterminado, entonces el propio Map tiene un método getOrElse :

 val map = mapOf("a" to 65,"b" to 66,"c" to 67) val something = map.getOrElse("z") { 0 } // provide default value in lambda something.toLong() // now valid 

Información de antecedentes:

Nota: en los ejemplos a continuación, estoy usando types explícitos para aclarar el comportamiento. Con la inferencia de tipo, normalmente los types pueden omitirse para variables locales y miembros privados.

Más sobre el the !! seguro operador

El !! el operador afirma que el valor no es null o arroja un NPE. Esto debería usarse en casos donde el desarrollador garantiza que el valor nunca será null . Piense en ello como una afirmación seguida de un elenco inteligente .

 val possibleXyz: Xyz? = ... // assert it is not null, but if it is throw an exception: val surelyXyz: Xyz = possibleXyz!! // same thing but access members after the assertion is made: possibleXyz!!.foo() 

leer más: !! Operador seguro


Más sobre verificación null y Smart Cast

Si protege el acceso a un tipo que admite null con una comprobación null , el comstackdor emitirá el valor dentro del cuerpo de la statement para que no sea nulo. Hay algunos flujos complicados donde esto no puede suceder, pero para casos comunes funciona bien.

 val possibleXyz: Xyz? = ... if (possibleXyz != null) { // allowed to reference members: possiblyXyz.foo() // or also assign as non-nullable type: val surelyXyz: Xyz = possibleXyz } 

O si lo hace, haga un control para un tipo que no admite nulos:

 if (possibleXyz is Xyz) { // allowed to reference members: possiblyXyz.foo() } 

Y lo mismo para las expresiones 'cuando' que también emiten de forma segura:

 when (possibleXyz) { null -> doSomething() else -> possibleXyz.foo() } // or when (possibleXyz) { is Xyz -> possibleXyz.foo() is Alpha -> possibleXyz.dominate() is Fish -> possibleXyz.swim() } 

Algunas cosas no permiten la verificación null de Smart Cast para el uso posterior de la variable. El ejemplo anterior usa una variable local que de ninguna manera podría haber mutado en el flujo de la aplicación, ya sea val o var esta variable no tuvo la oportunidad de mutar en un null . Pero, en otros casos donde el comstackdor no puede garantizar el análisis de flujo, esto sería un error:

 var nullableInt: Int? = ... public fun foo() { if (nullableInt != null) { // Error: "Smart cast to 'kotlin.Int' is impossible, because 'nullableInt' is a mutable property that could have been changed by this time" val nonNullableInt: Int = nullableInt } } 

El ciclo de vida de la variable nullableInt no es completamente visible y puede asignarse desde otros subprocesss, la verificación null no se puede convertir inteligentemente en un valor que no admite null . Consulte el tema "Llamadas seguras" a continuación para get una solución alternativa.

Otro caso en el que un molde inteligente no puede confiar para no mutar es una propiedad val en un object que tiene un getter personalizado. En este caso, el comstackdor no tiene visibilidad de qué muta el valor y, por lo tanto, recibirá un post de error:

 class MyThing { val possibleXyz: Xyz? get() { ... } } // now when referencing this class... val thing = MyThing() if (thing.possibleXyz != null) { // error: "Kotlin: Smart cast to 'kotlin.Int' is impossible, because 'px' is a property that has open or custom getter" thing.possiblyXyz.foo() } 

leer más: Comprobación de null en condiciones


Más sobre el ?. Operador de llamada segura

El operador de llamada segura devuelve nulo si el valor a la izquierda es nulo, de lo contrario continúa evaluando la expresión a la derecha.

 val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable() // "answer" will be null if any step of the chain is null val answer = possibleXyz?.foo()?.goo()?.boo() 

Otro ejemplo en el que desea iterar una list pero solo si no es null y no está vacío, de nuevo el operador de llamada segura es útil:

 val things: List? = makeMeAListOrDont() things?.forEach { // this loops only if not null (due to safe call) nor empty (0 items loop 0 times): } 

En uno de los ejemplos anteriores, tuvimos un caso en el que hicimos una comprobación if , pero tenemos la posibilidad de que otro hilo haya mutado el valor y, por lo tanto, no haya lanzado inteligente . Podemos cambiar esta muestra para usar el operador de llamada segura junto con la function let para resolver esto:

 var possibleXyz: Xyz? = 1 public fun foo() { possibleXyz?.let { value -> // only called if not null, and the value is captunetworking by the lambda val surelyXyz: Xyz = value } } 

leer más: llamadas seguras


Más sobre el ?: Elvis Operator

El operador de Elvis le permite proporcionar un valor alternativo cuando una expresión a la izquierda del operador es null :

 val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz() 

También tiene algunos usos creativos, por ejemplo arrojar una exception cuando algo es null :

 val currentUser = session.user ?: throw Http401Error("Unauthorized") 

o para regresar temprano de una function:

 fun foo(key: String): Int { val startingCode: String = codes.findKey(key) ?: return 0 // ... return endingValue } 

leer más: Elvis Operator


Operadores nulos con funciones relacionadas

Kotlin stdlib tiene una serie de funciones que funcionan muy bien con los operadores mencionados anteriormente. Por ejemplo:

 // use ?.let() to change a not null value, and ?: to provide a default val something = possibleNull?.let { it.transform() } ?: defaultSomething // use ?.apply() to operate further on a value that is not null possibleNull?.apply { func1() func2() } // use .takeIf or .takeUnless to turn a value null if it meets a pnetworkingicate val something = name.takeIf { it.isNotBlank() } ?: defaultName val something = name.takeUnless { it.isBlank() } ?: defaultName 

Temas relacionados

En Kotlin, la mayoría de las aplicaciones intentan evitar valores null , pero no siempre es posible. Y a veces null tiene perfecto sentido. Algunas pautas para pensar:

  • en algunos casos, garantiza diferentes types de devolución que incluyen el estado de la llamada al método y el resultado si tiene éxito. Bibliotecas como Result le dan un tipo de resultado de éxito o fracaso que también puede ramificar su código. Y la biblioteca de Protables para Kotlin llamada Kovenant hace lo mismo en forma de promises.

  • para las collections como types devueltos siempre devuelve una colección vacía en lugar de una null , a less que necesite un tercer estado de "no presente". Kotlin tiene funciones de ayuda como emptyList() o emptySet() para crear estos valores vacíos.

  • cuando utilice methods que devuelven un valor que admite valores NULL para los que tiene un valor pnetworkingeterminado o una alternativa, use el operador de Elvis para proporcionar un valor pnetworkingeterminado. En el caso de un Map use el getOrElse() que permite que se genere un valor pnetworkingeterminado en lugar del método Map get() que devuelve un valor que admite valores NULL. Lo mismo para getOrPut()

  • Al replace los methods de Java donde Kotlin no está seguro acerca de la nulabilidad del código de Java, siempre puede soltar el ? Nulibilidad de su anulación si está seguro de cuál debe ser la firma y la funcionalidad. Por lo tanto, su método reemplazado es más seguro. Lo mismo para implementar interfaces Java en Kotlin, cambie la capacidad de anulación para que sea lo que sabe que es válido.

  • Mire las funciones que ya pueden ayudar, como String?.isNullOrEmpty() y String?.isNullOrBlank() que pueden operar en un valor que admite valores de nulo y hacer lo que usted espera. De hecho, puede agregar sus propias extensiones para completar cualquier brecha en la biblioteca estándar.

  • aserción funciona como checkNotNull() y requireNotNull() en la biblioteca estándar.

  • helper funciona como filterNotNull() que elimina los filterNotNull() nulos de las collections, o listOfNotNull() para devolver una list de elementos de cero o de un valor posiblemente null .

  • también hay un operador de conversión Segura (nulable) que permite que un lanzamiento a tipo no nulo sea nulo si no es posible. Pero no tengo un caso de uso válido para esto que no se resuelva con los otros methods mencionados anteriormente.

La respuesta anterior es un acto difícil de seguir, pero aquí hay una manera rápida y fácil:

 val something: Xyz = createPossiblyNullXyz() ?: throw RuntimeError("no it shouldn't be null") something.foo() 

Si realmente nunca es nulo, la exception no ocurrirá, pero si alguna vez es así, verá lo que salió mal.

Kotlin no permite inicializar object nulo, por lo tanto, kotlin es Seguridad nula, no puede desaceleración nula