O que está escondido atrás do ponto de interrogação? Mergulhando no nullable em Kotlin
\n\nVocê já escreveu em Java e pegou um NullPointerException no pior momento possível? Se sim — você sabe como isso dói. Os desenvolvedores do Kotlin resolveram esse problema de uma vez por todas, tornando a null safety não apenas um recurso, mas uma filosofia da linguagem. Mas os iniciantes frequentemente se assustam com a pergunta: "In Kotlin, what is the idiomatic way to deal with nullable values, referencing or converting them?" (é assim que soa a pergunta mais popular no Stack Overflow).
Hoje vamos analisar todas as expressões idiomáticas para trabalhar com nullable: desde o simples ?. até o astuto let e o mágico !!. E o mais importante — vamos olhar por baixo dos panos para entender como isso funciona no nível do bytecode. Vamos nessa!
Breve introdução: o que é nullable?
\n\nEm Kotlin, os tipos são divididos em dois grupos: nullable (podem ser null) e non-nullable (não podem). Se você escrever var name: String = null — o compilador dará erro. Já var name: String? = null — ok. O ponto de interrogação é um marcador que diz: "Ei, compilador, eu sei que aqui pode estar vazio, não reclame".
Mas como trabalhar com isso? Simplesmente verificar se é null com if — é chato e não é o estilo Kotlin. Vamos ver as ferramentas reais.
A trindade salvadora: ?, ?: e ?.
\n\nKotlin oferece três pilares que sustentam todo o trabalho com nullable. Lembre-se deles — eles vão economizar horas de depuração.
\n\n1. Safe call operator (?.)
\n\nEste é o convidado mais frequente no seu código. O operador safe call permite chamar um método ou propriedade apenas se o objeto não for null. Se o objeto for null — o operador retorna null, e nenhuma exceção.
\n\nval user: User? = null\nval name: String? = user?.name // name = null, nenhum erro\n\nPor baixo dos panos, isso se transforma em bytecode que faz uma verificação comum de null e uma chamada condicional. Nenhuma mágica, apenas segurança.
\n\n2. Elvis operator (?:)
\n\nElvis é seu amigo quando você precisa definir um valor padrão. Se a parte esquerda não for null — pegamos ela, caso contrário — o que está à direita.
\n\nval name: String? = null\nval displayName: String = name ?: "Convidado" // "Convidado"\n\nElvis também pode ser usado para saída antecipada de uma função:
\n\nfun getUserName(user: User?): String {\n val name = user?.name ?: return "Anônimo"\n return name\n}\n\n3. Not-null assertion (!!)
\n\nEste é o operador rebelde. Ele diz: "Tenho certeza que aqui não é null, e estou pronto para morrer (no sentido de lançar um NPE) se estiver errado". Use com cuidado, apenas quando tiver 100% de certeza.
\n\nval user: User? = getUserFromDatabase() // temos certeza que ele existe\nval name: String = user!!.name\n\nPor baixo dos panos, !! é compilado em uma verificação comum com lançamento de NullPointerException se for null. Nada complicado, mas perigoso.
Forma idiomática: let, run, apply e companhia
\n\nAgora vamos ao mais interessante. A pergunta no Stack Overflow é exatamente sobre isso: como transformar valores nullable de forma idiomática. A resposta — funções de extensão da biblioteca padrão.
\n\nlet — o rei das transformações nullable
\n\nA função let executa um bloco de código apenas se o objeto não for null. Dentro do bloco, o objeto está disponível como it (ou pode ser renomeado).
val user: User? = getUser()\nuser?.let { \n println("Nome do usuário: ${it.name}")\n // Aqui it — garantidamente não é null\n}\n\nEsta é a forma idiomática de executar uma ação apenas quando o valor não é null. Sem o let, teria que escrever:
if (user != null) {\n println("Nome do usuário: ${user.name}")\n}