Primeiro, você deve ler tudo sobre a Segurança Nula no Kotlin, que cobre os casos completamente.
No Kotlin, você não pode acessar um valor anulável sem ter certeza de que não está null
( verificando condições nulas ) ou afirmando que certamente não está null
usando o !!
operador sure , acessando-o com uma ?.
chamada segura ou, finalmente, fornecendo algo que seja possivelmente null
um valor padrão usando o ?:
Elvis Operator .
Para o seu primeiro caso na sua pergunta, você tem opções, dependendo da intenção do código, você usaria uma delas e todas são idiomáticas, mas têm 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 "Por que funciona quando marcado nulo", leia as informações de plano de fundo abaixo em transmissões inteligentes .
Para o seu segundo caso na sua pergunta na pergunta com Map
, se você como desenvolvedor tiver certeza de que o resultado nunca será null
, use o !!
operador sure como uma afirmação:
val map = mapOf("a" to 65,"b" to 66,"c" to 67)
val something = map.get("a")!!
something.toLong() // now valid
ou em outro caso, quando o mapa PODE retornar um valor nulo, mas você pode fornecer um valor padrão, ele Map
próprio possui um getOrElse
método :
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
Informações básicas:
Nota: nos exemplos abaixo, estou usando tipos explícitos para tornar o comportamento claro. Com a inferência de tipo, normalmente os tipos podem ser omitidos para variáveis locais e membros privados.
Mais sobre o !!
operador certo
O !!
operador afirma que o valor não é null
ou gera um NPE. Isso deve ser usado nos casos em que o desenvolvedor está garantindo que o valor nunca seránull
. Pense nisso como uma afirmação seguida por um 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()
leia mais: !! Operador certo
Mais sobre null
verificação e conversão inteligente
Se você proteger o acesso a um tipo anulável com uma null
verificação, o compilador fará a conversão inteligente o valor no corpo da instrução como não anulável. Existem alguns fluxos complicados onde isso não pode acontecer, mas em casos comuns funciona bem.
val possibleXyz: Xyz? = ...
if (possibleXyz != null) {
// allowed to reference members:
possiblyXyz.foo()
// or also assign as non-nullable type:
val surelyXyz: Xyz = possibleXyz
}
Ou se você fizer um is
verificar um tipo não anulável:
if (possibleXyz is Xyz) {
// allowed to reference members:
possiblyXyz.foo()
}
E o mesmo para expressões 'when' que também lançam com segurança:
when (possibleXyz) {
null -> doSomething()
else -> possibleXyz.foo()
}
// or
when (possibleXyz) {
is Xyz -> possibleXyz.foo()
is Alpha -> possibleXyz.dominate()
is Fish -> possibleXyz.swim()
}
Algumas coisas não permitem que a null
verificação seja transmitida de forma inteligente para o uso posterior da variável. O exemplo acima usa uma variável local que de nenhuma maneira poderia ter mutado no fluxo da aplicação, se val
ou var
esta variável não teve oportunidade de se transformar em umnull
. Mas, em outros casos em que o compilador não pode garantir a análise de fluxo, isso seria um erro:
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
}
}
O ciclo de vida da variável nullableInt
não é completamente visível e pode ser atribuído a partir de outros encadeamentos, a null
verificação não pode ser convertida de maneira inteligente em um valor não nulo. Consulte o tópico "Chamadas seguras" abaixo para obter uma solução alternativa.
Outro caso que não é confiável para uma conversão inteligente não ser mutada é uma val
propriedade em um objeto que possui um getter personalizado. Nesse caso, o compilador não tem visibilidade do que modifica o valor e, portanto, você receberá uma mensagem de erro:
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()
}
leia mais: Verificando condições nulas
Mais sobre o ?.
operador Chamada segura
O operador de chamada segura retorna nulo se o valor à esquerda for nulo; caso contrário, continua a avaliar a expressão à direita.
val possibleXyz: Xyz? = makeMeSomethingButMaybeNullable()
// "answer" will be null if any step of the chain is null
val answer = possibleXyz?.foo()?.goo()?.boo()
Outro exemplo em que você deseja iterar uma lista, mas apenas se não estiver null
vazio, novamente o operador de chamada segura será útil:
val things: List? = makeMeAListOrDont()
things?.forEach {
// this loops only if not null (due to safe call) nor empty (0 items loop 0 times):
}
Em um dos exemplos acima, tivemos um caso em que fizemos uma if
verificação, mas tivemos a chance de outro encadeamento alterar o valor e, portanto, nenhuma conversão inteligente . Podemos alterar este exemplo para usar o operador de chamada segura junto com a let
função para resolver isso:
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
}
}
leia mais: Chamadas seguras
Mais sobre o ?:
operador Elvis
O operador Elvis permite fornecer um valor alternativo quando uma expressão à esquerda do operador é null
:
val surelyXyz: Xyz = makeXyzOrNull() ?: DefaultXyz()
Ele também tem alguns usos criativos, por exemplo, lança uma exceção quando algo é null
:
val currentUser = session.user ?: throw Http401Error("Unauthorized")
ou retornar cedo de uma função:
fun foo(key: String): Int {
val startingCode: String = codes.findKey(key) ?: return 0
// ...
return endingValue
}
leia mais: Elvis Operator
Operadores nulos com funções relacionadas
O Kotlin stdlib possui uma série de funções que funcionam muito bem com os operadores mencionados acima. Por exemplo:
// 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
Tópicos relacionados
No Kotlin, a maioria dos aplicativos tenta evitar null
valores, mas nem sempre é possível. E às vezes null
faz todo sentido. Algumas diretrizes para se pensar:
em alguns casos, garante diferentes tipos de retorno que incluem o status da chamada do método e o resultado, se for bem-sucedido. Bibliotecas como Result oferecem um tipo de resultado com êxito ou falha que também pode ramificar seu código. E a biblioteca Promessas para Kotlin, chamada Kovenant, faz o mesmo na forma de promessas.
para coleções como tipos de retorno sempre retornam uma coleção vazia em vez de a null
, a menos que você precise de um terceiro estado de "não presente". O Kotlin possui funções auxiliares como emptyList()
ouemptySet()
para criar esses valores vazios.
ao usar métodos que retornam um valor nulo para o qual você tem um padrão ou alternativa, use o operador Elvis para fornecer um valor padrão. No caso de um Map
uso, o getOrElse()
que permite a geração de um valor padrão em vez do Map
método get()
que retorna um valor nulo. O mesmo paragetOrPut()
ao substituir métodos de Java nos quais o Kotlin não tem certeza sobre a nulidade do código Java, você sempre pode eliminar a ?
nulidade de sua substituição, se tiver certeza de qual deve ser a assinatura e a funcionalidade. Portanto, seu método substituído é maisnull
seguro. O mesmo para implementar interfaces Java no Kotlin, altere a nulidade para ser o que você sabe que é válido.
observe as funções que já podem ajudar, como para String?.isNullOrEmpty()
e String?.isNullOrBlank()
que podem operar com segurança em um valor nulo e fazer o que você espera. De fato, você pode adicionar suas próprias extensões para preencher quaisquer lacunas na biblioteca padrão.
asserção funciona como checkNotNull()
e requireNotNull()
na biblioteca padrão.
funções auxiliares filterNotNull()
que removem valores nulos de coleções ou listOfNotNull()
para retornar uma lista de zero ou único item de um null
valor possível .
existe também um operador de conversão Seguro (anulável) que permite que uma conversão para o tipo não anulável retorne nulo se não for possível. Mas não tenho um caso de uso válido para isso que não seja resolvido pelos outros métodos mencionados acima.