nullable - elvis - let kotlin null
En Kotlin, ¿cuál es la forma idiomática de tratar con valores anulables, hacer referencia o convertirlos? (2)
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 excepción no sucederá, pero si alguna vez lo es, verá lo que salió mal.
Si tengo un tipo anulable
Xyz?
, Quiero hacer referencia a él o convertirlo a un tipo no anulable
Xyz
.
¿Cuál es la forma idiomática de hacerlo en Kotlin?
Por ejemplo, este código tiene un 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 primero marco nulo 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 esté seguro de que nunca es
null
?
Por ejemplo, aquí estoy recuperando un valor de un mapa 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 falte el elemento y devuelve el tipo
Int?
.
Por lo tanto, ¿cuál es la mejor manera de forzar que el tipo del valor no sea anulable?
Nota: esta pregunta está escrita y respondida intencionalmente por el autor ( Preguntas con respuesta propia ), de modo que las respuestas idiomáticas a los temas de Kotlin más frecuentes están presentes en SO. También para aclarar algunas respuestas realmente antiguas escritas para alfas de Kotlin que no son precisas para el Kotlin actual.
Primero, debe leer todo sobre Seguridad nula en Kotlin, que cubre los casos a fondo.
En Kotlin, no puede acceder a un valor anulable sin estar seguro de que no es
null
(
Verificando si es nulo en condiciones
), o afirmando que seguramente no es
null
usando el
!!
operador seguro
, accediendo con un
?.
Safe Call
, o por último dar algo que posiblemente sea un valor predeterminado 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 resultados diferentes:
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 nulo marcado", lea la información de fondo a continuación sobre los modelos inteligentes .
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
!!
operador seguro 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 mapa PODRÍA devolver un valor nulo pero puede proporcionar un valor predeterminado, el
Map
sí 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 tipos explícitos para aclarar el comportamiento. Con la inferencia de tipos, normalmente los tipos se pueden omitir para variables locales y miembros privados.
Más sobre el
!!
operador seguro
El
!!
El operador afirma que el valor no es
null
o arroja un NPE.
Esto debería usarse en casos donde el desarrollador está garantizando 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 cuentas
null
y Smart Casts
Si protege el acceso a un tipo anulable con una comprobación
null
, el compilador emitirá de manera
inteligente
el valor dentro del cuerpo de la declaración para que no sea anulable.
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 hace una comprobación de un tipo no anulable:
if (possibleXyz is Xyz) {
// allowed to reference members:
possiblyXyz.foo()
}
Y lo mismo para las expresiones ''when'' que también son seguras:
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
la
null
inteligente
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 a un
null
.
Pero, en otros casos donde el compilador 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 subprocesos, la verificación
null
no se puede convertir de manera
inteligente
en un valor no anulable.
Consulte el tema "Llamadas seguras" a continuación para obtener una solución alternativa.
Otro caso en el que un
elenco inteligente
no puede confiar para no mutar es una propiedad
val
en un objeto que tiene un captador personalizado.
En este caso, el compilador no tiene visibilidad de lo que muta el valor y, por lo tanto, recibirá un mensaje 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 ''p.x'' is a property that has open or custom getter"
thing.possiblyXyz.foo()
}
Leer más: Verificación de nulo 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 lista pero solo si no es
null
y no está vacío, nuevamente 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 tuvimos la posibilidad de que otro hilo mutara el valor y, por lo tanto, no se
emitiera de forma inteligente
.
Podemos cambiar esta muestra para usar el operador de llamada segura junto con la función
let
para resolver esto:
var possibleXyz: Xyz? = 1
public fun foo() {
possibleXyz?.let { value ->
// only called if not null, and the value is captured by the lambda
val surelyXyz: Xyz = value
}
}
Leer más: Llamadas seguras
Más sobre el
?:
Operador de Elvis
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, lanzar una excepción cuando algo es
null
:
val currentUser = session.user ?: throw Http401Error("Unauthorized")
o regresar temprano de una función:
fun foo(key: String): Int {
val startingCode: String = codes.findKey(key) ?: return 0
// ...
return endingValue
}
Leer más: Operador de Elvis
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 predicate
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 mucho sentido.
Algunas pautas para pensar:
-
en algunos casos, garantiza diferentes tipos de devolución que incluyen el estado de la llamada al método y el resultado si tiene éxito. Las bibliotecas como Result dan un tipo de resultado de éxito o fracaso que también puede ramificar su código. Y la biblioteca de Promesas para Kotlin llamada Kovenant hace lo mismo en forma de promesas.
-
para colecciones como tipos de retorno siempre devuelve una colección vacía en lugar de una
null
, a menos que necesite un tercer estado de "no presente". Kotlin tiene funciones auxiliares comoemptyList()
oemptySet()
para crear estos valores vacíos. -
cuando utilice métodos que devuelvan un valor anulable para el que tiene un valor predeterminado o alternativo, use el operador Elvis para proporcionar un valor predeterminado. En el caso de un
Map
usegetOrElse()
que permite generar un valor predeterminado en lugar del método deMap
get()
que devuelve un valor anulable. Lo mismo paragetOrPut()
-
al anular métodos de Java donde Kotlin no está seguro acerca de la nulabilidad del código Java, siempre puede soltar el
?
nulabilidad de su anulación si está seguro de cuál debería ser la firma y la funcionalidad. Por lo tanto, su método anulado es másnull
seguro. Lo mismo para implementar interfaces Java en Kotlin, cambie la nulabilidad para que sea lo que sabe que es válido. -
mire las funciones que ya pueden ayudar, como
String?.isNullOrEmpty()
yString?.isNullOrBlank()
que pueden operar en un valor anulable de forma segura y hacer lo que espera. De hecho, puede agregar sus propias extensiones para llenar cualquier espacio en la biblioteca estándar. -
La aserción funciona como
checkNotNull()
yrequireNotNull()
en la biblioteca estándar. -
funciones auxiliares como
filterNotNull()
que eliminan los nulos de las colecciones, olistOfNotNull()
para devolver una lista de elementos cero o individual de un valor posiblementenull
. -
también hay un operador de conversión Segura (anulable) que permite que una conversión a tipo no anulable devuelva nulo si no es posible. Pero no tengo un caso de uso válido para esto que no se resuelva con los otros métodos mencionados anteriormente.