O que é Nullable em Kotlin?

Em Kotlin, todo tipo é não-nulo por padrão. Se você declara uma variável como String, ela não pode receber null. Para permitir valores nulos, você precisa adicionar ? ao tipo, criando um tipo nullable como String?.

Essa é uma das maiores sacadas do Kotlin: o famoso NullPointerException é praticamente eliminado em tempo de compilação.

Declarando tipos nullable

var nome: String = "Karina"
// nome = null  // Erro de compilacao!

var apelido: String? = "Kah"
apelido = null  // Tudo certo!

Operador de chamada segura ?.

O operador ?. só executa a operação se o valor não for nulo:

val cidade: String? = "São Paulo"
println(cidade?.length)    // 9

val estado: String? = null
println(estado?.length)    // null (nao dá erro!)

Operador Elvis ?:

O operador Elvis fornece um valor padrão quando o original é nulo:

val nome: String? = null
val exibicao = nome ?: "Anônimo"
println(exibicao) // Anônimo

Combina muito bem com o operador ?.:

fun tamanhoDoNome(nome: String?): Int {
    return nome?.length ?: 0
}

println(tamanhoDoNome("Fernanda")) // 8
println(tamanhoDoNome(null))       // 0

Operador de asserção !!

O !! força a conversão para não-nulo, mas lança exceção se o valor for null:

val valor: String? = "Kotlin"
println(valor!!.length) // 6

val nulo: String? = null
// println(nulo!!.length) // NullPointerException!

Use !! com muito cuidado — só quando você tem certeza absoluta de que o valor não é nulo.

Smart cast com if

O Kotlin faz smart cast automaticamente após verificações de null:

fun imprimir(texto: String?) {
    if (texto != null) {
        // Aqui o compilador já sabe que texto nao é null
        println(texto.uppercase())
    }
}

Encadeamento seguro com let

A função let combinada com ?. é uma das formas mais elegantes de trabalhar com nullable:

data class Endereco(val rua: String?, val cidade: String?)
data class Usuario(val nome: String, val endereco: Endereco?)

fun exibirCidade(usuario: Usuario) {
    usuario.endereco?.cidade?.let { cidade ->
        println("O usuário mora em $cidade")
    } ?: println("Cidade nao informada")
}

fun main() {
    val usuario1 = Usuario("Ana", Endereco("Rua A", "Curitiba"))
    val usuario2 = Usuario("Pedro", null)

    exibirCidade(usuario1) // O usuário mora em Curitiba
    exibirCidade(usuario2) // Cidade nao informada
}

Coleções e Nullable

Kotlin diferencia entre coleções que podem conter elementos nulos e coleções que podem ser nulas:

// Lista que pode conter elementos nulos
val nomes: List<String?> = listOf("Ana", null, "Carlos", null)
val naoNulos = nomes.filterNotNull()
println(naoNulos) // [Ana, Carlos]

// Lista que pode ser nula
val lista: List<String>? = null
println(lista?.size ?: 0) // 0

// Lista que pode ser nula E conter elementos nulos
val mista: List<String?>? = listOf("Kotlin", null, "Brasil")
val resultado = mista?.filterNotNull()?.joinToString()
println(resultado) // Kotlin, Brasil

Casos de Uso no Mundo Real

  • Dados vindos de APIs externas: respostas JSON frequentemente contêm campos opcionais. Usar tipos nullable permite modelar esses campos de forma segura sem arriscar crashes em tempo de execução.
  • Formulários de cadastro: campos não obrigatórios como apelido, telefone secundário ou foto de perfil são naturalmente nullable na modelagem do domínio.
  • Integração com código Java: bibliotecas Java podem retornar null em qualquer lugar. Os tipos nullable do Kotlin servem como camada de proteção nessa integração.
  • Busca em banco de dados: consultas que podem não encontrar resultados retornam null naturalmente, como findById() que retorna Usuario?.

Boas Práticas

  • Prefira ?. e ?: em vez de !!. O operador !! deve ser a exceção, não a regra.
  • Use let para trabalhar com nullable: valor?.let { println(it) }.
  • Evite tipos nullable quando possível — menos ?, menos problemas. Se um valor sempre vai existir, declare como não-nulo.
  • Valide valores nullable nas bordas do sistema (entrada de dados, respostas de API) e converta para tipos não-nulos o mais cedo possível.
  • Use requireNotNull() ou checkNotNull() para válidações explícitas com mensagens de erro descritivas, em vez de !!.

Erros Comuns

  • Usar !! em toda parte: isso destrói a segurança contra nulos do Kotlin. Cada !! é um potencial NullPointerException esperando para acontecer.
  • Ignorar avisos do compilador sobre platform types: quando você usa código Java, o Kotlin mostra tipos como String! (platform type). Sempre trate esses tipos como nullable para evitar surpresas.
  • Criar cadeias longas de ?.: encadeamentos como a?.b?.c?.d?.e indicam que o modelo de dados pode estar mal estruturado. Considere refatorar.
  • Usar nullable para valores com default: se um campo sempre tem um valor padrão, use esse valor em vez de null. Por exemplo, val nome: String = "" é melhor que val nome: String? = null quando string vazia é aceitável.
  • Esquecer de tratar o caso null: usar apenas ?. sem ?: pode resultar em null silencioso propagando pelo sistema sem que ninguém perceba.

Perguntas Frequentes

Qual a diferença entre String e String? em Kotlin? String é um tipo não-nulo que nunca pode receber null. String? é o tipo nullable equivalente, que aceita tanto valores de texto quanto null. O compilador trata os dois como tipos diferentes e exige operadores seguros ao trabalhar com String?.

Quando devo usar !! em vez de ?.? Praticamente nunca. O !! deve ser usado apenas quando você tem garantia lógica absoluta de que o valor não é nulo e quer que o programa falhe caso esteja errado. Prefira requireNotNull() que permite incluir uma mensagem de erro explicativa.

O Kotlin elimina completamente o NullPointerException? Não completamente, mas reduz drasticamente. NPEs ainda podem ocorrer ao usar !!, ao interagir com código Java, ou em cenários de concorrência. A diferença é que, no Kotlin, quase todo NPE é uma escolha consciente do desenvolvedor.

Como lidar com nullable em interoperabilidade com Java? Trate todos os tipos vindos de Java como nullable por padrão, a menos que a biblioteca Java use anotações como @NotNull ou @Nullable. Use ?. e ?: para criar uma camada de segurança entre o código Kotlin e o Java.

Termos Relacionados

  • Data Class — classes de dados que frequentemente contêm propriedades nullable
  • Scope Functionslet, run e outras funções úteis para trabalhar com nullable
  • Smart Cast — conversão automática de tipos após verificações de null
  • Extension Function — funções de extensão que podem ser declaradas para tipos nullable