Usar a nulidade no Kotlin

1. Antes de começar

Este codelab ensina sobre a nulidade e a importância da proteção contra valores null. Nulidade é um conceito geralmente encontrado em muitas linguagens de programação. O termo se refere à capacidade de variáveis não terem um valor. No Kotlin, a nulidade é tratada intencionalmente para proteger contra valores null.

Pré-requisitos

  • Conhecimentos básicos de programação em Kotlin, incluindo variáveis e as funções println() e main().
  • Familiaridade com os condicionais do Kotlin, incluindo instruções if/else e expressões booleanas.
  • Conhecimento sobre classes do Kotlin, incluindo como acessar métodos e propriedades de uma variável.

O que você vai aprender

  • O que é null.
  • A diferença entre tipos anuláveis e não anuláveis.
  • O que é a proteção contra valores null, a importância dela e como a proteção contra valores null é oferecida em Kotlin.
  • Como acessar métodos e propriedades de variáveis anuláveis com o operador de chamada segura ?. e o operador de declaração não nula !!.
  • Como executar verificações de valores null com condicionais if/else.
  • Como converter uma variável anulável em um tipo não anulável com expressões if/else.
  • Como usar a expressão if/else ou o operador Elvis ?: para fornecer um valor padrão quando uma variável anulável for null.

O que é necessário

  • Um navegador da Web com acesso ao Playground Kotlin.

2. Usar variáveis anuláveis

O que é o valor null?

Na Unidade 1, você aprendeu que, ao declarar uma variável, é preciso atribuir um valor a ela imediatamente. Por exemplo, ao declarar uma variável favoriteActor, é possível atribuir um valor de string "Sandra Oh" imediatamente.

val favoriteActor = "Sandra Oh"

Uma caixa que representa uma variável favoriteActor que recebe um valor de string "Sandra Oh".

Mas e se você não tiver uma atriz favorita? Você pode atribuir um valor "Nobody" ou "None" à variável. Essa não é uma boa opção porque o programa interpreta a variável favoriteActor como se tivesse um valor "Nobody" ou "None", em vez de nenhum valor. No Kotlin, você pode usar null para indicar que não há um valor associado à variável.

Uma caixa que representa a variável favoriteActor que recebeu um valor nulo.

Para usar null no código, siga estas etapas:

  1. No Playground Kotlin, substitua o conteúdo no corpo da função main() por uma variável favoriteActor definida como null:
fun main() {
    val favoriteActor = null
}
  1. Mostre o valor da variável favoriteActor com a função println() e execute este programa:
fun main() {
    val favoriteActor = null
    println(favoriteActor)
}

A resposta vai ser parecida com este snippet de código:

null

Reatribuições de variáveis com valor null

Anteriormente, você aprendeu que é possível reatribuir variáveis definidas com a palavra-chave var a valores diferentes do mesmo tipo. Por exemplo, você pode reatribuir uma variável name declarada com um nome a outro nome, desde que o novo nome seja do tipo String.

var favoriteActor: String = "Sandra Oh"
favoriteActor = "Meryl Streep"

Há situações em que você pode declarar uma variável quando quiser defini-la como null. Por exemplo, depois de declarar a atriz favorita, você decide que não quer mais mostrar essa informação. Nesse caso, é útil definir a variável favoriteActor como null.

Entender variáveis não anuláveis e anuláveis

Para reatribuir a variável favoriteActor a null, siga estas etapas:

  1. Mude a palavra-chave val para var, depois, especifique que a variável favoriteActor é do tipo String e atribua-a ao nome da atriz favorita:
fun main() {
    var favoriteActor: String = "Sandra Oh"
    println(favoriteActor)
}
  1. Remova a função println():
fun main() {
    var favoriteActor: String = "Sandra Oh"
}
  1. Reatribua a variável favoriteActor a null e execute este programa:
fun main() {
    var favoriteActor: String = "Sandra Oh"
    favoriteActor = null
}

Você vai ver esta mensagem de erro:

Uma mensagem de aviso que diz: "Null cannot be a value of a non-null type String" (não é possível um valor ser nulo em uma string de tipo não nulo).

No Kotlin, há uma distinção entre tipos anuláveis e não anuláveis:

  • Os tipos anuláveis são variáveis que podem conter null.
  • Os tipos não nulos são variáveis que não podem conter null.

Para um tipo ser anulável, é necessário permitir explicitamente que ele contenha null. Como a mensagem de erro informa, o tipo de dados String é não anulável, ou seja, não é possível reatribuir a variável a null.

Um diagrama que mostra como declarar variáveis de tipo anulável. Ele começa com uma palavra-chave "var" seguida pelo nome do bloco de variáveis, um ponto e vírgula, o tipo da variável, um ponto de interrogação, o sinal de igual e o bloco de valor.  O bloco de tipo e o ponto de interrogação são marcados com "Nullable type" (tipo anulável). O tipo seguido por um ponto de interrogação é o que faz dele um tipo anulável.

Para declarar variáveis anuláveis no Kotlin, é necessário adicionar um operador ? ao final do tipo. Por exemplo, um tipo String? pode conter uma string ou null, já um tipo String pode conter apenas uma string. Para declarar uma variável anulável, você precisa adicionar explicitamente o tipo anulável. Sem o tipo anulável, o compilador do Kotlin infere que o tipo não é anulável.

  1. Mude o tipo de variável favoriteActor de String para String?:
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    favoriteActor = null
}
  1. Mostre a variável favoriteActor antes e depois da reatribuição de null e execute este programa:
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor)

    favoriteActor = null
    println(favoriteActor)
}

A resposta vai ser parecida com este snippet de código:

Sandra Oh
null

Originalmente, a variável favoriteActor continha uma string que foi convertida em null.

Testar

Agora que você pode usar o tipo String? anulável, tente inicializar uma variável com um valor Int e reatribuí-la a null.

Criar um valor Int anulável

  1. Remova todo o código na função main():
fun main() {

}
  1. Crie uma variável number de um tipo Int anulável e atribua a ela um valor 10:
fun main() {
    var number: Int? = 10
}
  1. Mostre a variável number e execute este programa:
fun main() {
    var number: Int? = 10
    println(number)
}

A saída é a esperada:

10
  1. Reatribua a variável number a null para confirmar que ela é anulável:
fun main() {
    var number: Int? = 10
    println(number)

    number = null
}
  1. Adicione outra instrução println(number) como a linha final do programa e o execute:
fun main() {
    var number: Int? = 10
    println(number)

    number = null
    println(number)
}

A saída é a esperada:

10
null

3. Processar variáveis anuláveis

Você já aprendeu a usar o operador . para acessar métodos e propriedades de variáveis não anuláveis. Nesta seção, você vai aprender a usá-lo para acessar métodos e propriedades de variáveis anuláveis.

Para acessar uma propriedade da variável favoriteActor não anulável, siga estas etapas:

  1. Remova todo o código na função main(), declare uma variável favoriteActor do tipo String e atribua o nome da sua atriz favorita a ela:
fun main() {
    var favoriteActor: String = "Sandra Oh"
}
  1. Mostre o número de caracteres no valor da variável favoriteActor com a propriedade length (link em inglês) e execute este programa:
fun main() {
    var favoriteActor: String = "Sandra Oh"
    println(favoriteActor.length)
}

A saída é a esperada:

9

nove caracteres no valor da variável favoriteActor, o que inclui espaços. O número de caracteres no nome da atriz favorita pode ser diferente.

Acessar uma propriedade de uma variável anulável

Imagine que você queira tornar a variável favoriteActor anulável para que as pessoas que não têm uma atriz possam atribuir a variável a null.

Para acessar uma propriedade da variável favoriteActor anulável, siga estas etapas:

  • Mude o tipo da variável favoriteActor para um tipo anulável e execute este programa:
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor.length)
}

Você vai ver esta mensagem de erro:

Uma mensagem de erro que diz "Only safe or non-null asserted calls are allowed on a nullable receiver of type String?" (apenas chamadas declaradas protegidas ou não nulas são permitidas em um receptor anulável do tipo String?).

Isso é um erro de compilação. Como mencionado em um codelab anterior, um erro de compilação acontece quando o Kotlin não compila o código devido a um erro de sintaxe.

O Kotlin aplica regras sintáticas para oferecer proteção contra valores null, ou seja, para garantir que nenhuma chamada acidental seja feita em variáveis potencialmente null. Isso não significa que as variáveis não podem ser null. Isso significa que, se um membro de uma variável for acessado, ela não pode ser null.

Isso é fundamental porque se houver uma tentativa de acessar um membro de uma variável que é null, o que é conhecido como referência null, durante a execução de um app, o app falha porque null não contém nenhuma propriedade ou método. Esse tipo de falha é conhecido como erro de execução e ocorre depois que o código é compilado e executado.

Devido à natureza da proteção contra valores null do Kotlin, esses erros de execução são evitados porque o compilador do Kotlin força uma verificação de valores null em tipos anuláveis. A verificação de valores Null é um processo que verifica se uma variável pode ser null antes de ser acessada e tratada como um tipo não anulável. Se você quiser usar um valor anulável como o tipo não anulável, é necessário realizar uma verificação de valores null explicitamente. Você vai aprender mais sobre isso na seção Usarif/else condicionais mais adiante neste codelab.

Nesse exemplo, o código falha durante a compilação porque a referência direta à propriedade length da variável favoriteActor não é permitida porque há a possibilidade de que a variável seja null.

Em seguida, você vai aprender várias técnicas e operadores para trabalhar com tipos anuláveis.

Usar o operador de chamada segura ?.

Você pode usar o operador de chamada segura ?. para acessar métodos ou propriedades de variáveis anuláveis.

Um diagrama que mostra um bloco de variáveis anuláveis seguido por um ponto de interrogação, um ponto e um bloco de propriedade ou método. Não há espaços entre eles.

Para usar o operador de chamada segura ?. para acessar um método ou propriedade, adicione um símbolo ? após o nome da variável e acesse o método ou a propriedade com a notação ..

O operador de chamada segura ?. permite acesso mais seguro a variáveis anuláveis porque o compilador do Kotlin interrompe qualquer tentativa de acesso de referência a membros null e retorna null para o membro acessado.

Para acessar com segurança uma propriedade da variável favoriteActor anulável, siga estas etapas:

  1. Na instrução println(), substitua o operador . pelo operador de chamada segura ?.:
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor?.length)
}
  1. Execute este programa e verifique se a saída é a esperada:
9

O número de caracteres do nome da atriz favorita pode ser diferente.

  1. Reatribua a variável favoriteActor a null e execute este programa:
fun main() {
    var favoriteActor: String? = null
    println(favoriteActor?.length)
}

Você vai ver esta saída:

null

O programa não falha, apesar da tentativa de acessar a propriedade length de uma variável null. A expressão de chamada segura simplesmente retorna null.

Usar o operador de declaração não nula !!

Também é possível usar o operador de declaração não nula !! para acessar métodos ou propriedades de variáveis anuláveis.

Um diagrama que mostra um bloco de variáveis anuláveis seguido por dois pontos de exclamação, um único ponto e um bloco de propriedade ou método. Não há espaços entre eles.

Depois da variável anulável, você precisa adicionar o operador de declaração não nula !! seguido pelo operador . e pelo método ou a propriedade sem espaços.

Como o nome sugere, se você usar a declaração não nula !!, isso significa que você declara que o valor não é null, independente dele ser ou não.

Diferente dos operadores de chamada segura ?., o uso de um operador de declaração não nula !! pode resultar em um erro de NullPointerException se a variável anulável for de fato null. Portanto, esse procedimento deve ser feito apenas quando a variável sempre for não anulável ou quando o processamento adequado de exceções estiver em vigor. Quando não são processadas, as exceções causam erros de execução. Você vai aprender sobre o gerenciamento de exceções nas próximas unidades deste curso.

Para acessar uma propriedade da variável favoriteActor com o operador de declaração não nula !!, siga estas etapas:

  1. Reatribua a variável favoriteActor ao nome da sua atriz favorita e substitua o operador de chamada segura ?. pelo operador de declaração não nula !! na instrução println():
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor!!.length)
}
  1. Execute este programa e verifique se a saída é a esperada:
9

O número de caracteres do nome da atriz favorita pode ser diferente.

  1. Reatribua a variável favoriteActor a null e execute este programa:
fun main() {
    var favoriteActor: String? = null
    println(favoriteActor!!.length)
}

Você vai ver um erro NullPointerException:

Uma mensagem de erro que diz "Exception in thread "main" java.lang.NullPointerException" (exceção na linha de execução "main" java.lang.NullPointerException).

Esse erro do Kotlin mostra que o programa falhou durante a execução. Por isso, não é recomendável usar o operador de declaração não nula !!, a menos que você tenha certeza de que a variável não é null.

Usar condicionais if/else

Você pode usar a ramificação if nos condicionais if/else para realizar verificações de valores null.

Um diagrama que mostra um bloco de variáveis anuláveis seguido por um ponto de exclamação, um sinal de igual e o valor nulo.

Para realizar as verificações de valores null, veja se a variável anulável não é igual a null com o operador de comparação !=.

Instruções if/else

Uma instrução if/else pode ser usada com uma verificação de valores null desta maneira:

Um diagrama que descreve uma instrução "if/else" com a palavra "if" seguida de parênteses com um bloco de verificação de valor nulo, um par de chaves com o corpo 1 dentro deles, uma palavra-chave "else" e outro par de chaves com o bloco "body 2" (corpo 2) dentro delas. A cláusula "else" é encapsulada com uma caixa vermelha pontilhada que é anotada como opcional.

A verificação de valores nulos é útil quando combinada com uma instrução if/else:

  • A verificação de valores null da expressão nullableVariable != null é usada como a condição if.
  • O corpo 1 na ramificação if pressupõe que a variável não é anulável. Nesse corpo, você pode acessar livremente métodos ou propriedades da variável como se ela fosse uma variável não anulável sem usar um operador de chamada segura ?. ou um operador de declaração não nula !!.
  • O corpo 2 na ramificação else pressupõe que a variável é null. Nesse corpo, é possível adicionar instruções que vão ser executadas quando a variável for null. A ramificação else é opcional. É possível usar apenas a condicional if para executar uma verificação de valores null sem fornecer uma ação padrão, quando a verificação de null falhar.

A verificação de valores null é mais conveniente para usar com a condição if quando há várias linhas de código que usam a variável anulável. Por outro lado, o operador de chamada segura ?. é mais conveniente para uma única referência da variável anulável.

Para criar uma instrução if/else com uma verificação de valores null para a variável favoriteActor, siga estas etapas:

  1. Atribua a variável favoriteActor ao nome da atriz favorita novamente e remova a instrução println():
fun main() {
    var favoriteActor: String? = "Sandra Oh"

}
  1. Adicione uma ramificação if com uma condição favoriteActor != null:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {

    }
}
  1. No corpo da ramificação if, adicione uma instrução println que aceite uma string "The number of characters in your favorite actor's name is ${favoriteActor.length}", em seguida, execute este programa:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    }
}

A saída é a esperada.

The number of characters in your favorite actor's name is 9.

O número de caracteres no nome da atriz favorita pode ser diferente.

É possível acessar o método de comprimento do nome diretamente com o operador . porque o método length é acessado dentro da ramificação if após a verificação de valores null. O compilador do Kotlin sabe que não há como a variável favoriteActor ser null. Por isso, ele permite o acesso direto à propriedade.

  1. Opcional: adicione uma ramificação else para processar uma situação em que o nome da atriz é null:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {

    }
}
  1. No corpo da ramificação else, adicione uma instrução println que use uma string "You didn't input a name.":
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}
  1. Atribua a variável favoriteActor a null e execute este programa:
fun main() {
    var favoriteActor: String? = null

    if(favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}

A saída é a esperada:

You didn't input a name.

Expressões if/else

Também é possível combinar a verificação de valores null com uma expressão if/else para converter uma variável anulável em uma não anulável.

Um diagrama que descreve uma expressão "if/else" com a palavra-chave val seguida por um bloco de nome, dois-pontos e um bloco de tipo não nulo, um símbolo de igual, a palavra-chave "if", parênteses com uma condição dentro deles, um par de chaves com "body 1" (corpo 1) dentro deles, uma palavra-chave "else" com outro par de chaves e um bloco "body 2" (corpo 2) dentro delas.

Para atribuir uma expressão if/else a um tipo não anulável:

  • A verificação nullableVariable != null null é usada como a condição if.
  • O corpo 1 na ramificação if pressupõe que a variável não é anulável. Nesse corpo, você pode acessar métodos ou propriedades da variável como se ela fosse uma variável não anulável sem um operador de chamada segura ?. ou um operador de declaração não nula !!.
  • O corpo 2 na ramificação else pressupõe que a variável é null. Nesse corpo, é possível adicionar instruções que são executadas quando a variável é null.
  • Na última linha do corpo 1 e 2, é necessário usar uma expressão ou um valor que resulte em um tipo não anulável para que ele seja atribuído à variável não anulável quando a verificação de valores null for aprovada ou reprovada, respectivamente.

Para usar a expressão if/else e reescrever o programa usando somente uma instrução println, siga estas etapas:

  1. Atribua a variável favoriteActor ao nome da atriz favorita:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}
  1. Crie uma variável lengthOfName e a atribua à expressão if/else:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}
  1. Remova as duas instruções println() das ramificações if e else:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {

    } else {

    }
}
  1. No corpo da ramificação if, adicione uma expressão favoriteActor.length:
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {
      favoriteActor.length
    } else {

    }
}

A propriedade length da variável favoriteActor é acessada diretamente com o operador ..

  1. No corpo da ramificação else, adicione um valor 0:
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {
      favoriteActor.length
    } else {
      0
    }
}

O valor 0 serve como o valor padrão, quando o nome for null.

  1. No final da função main(), adicione uma instrução println que use uma string "The number of characters in your favorite actor's name is $lengthOfName.", em seguida, execute este programa:
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if(favoriteActor != null) {
      favoriteActor.length
    } else {
      0
    }

    println("The number of characters in your favorite actor's name is $lengthOfName.")
}

A saída é a esperada:

The number of characters in your favorite actor's name is 9.

O número de caracteres do nome pode ser diferente.

Usar o operador Elvis ?:

O Elvis ?: é um operador que pode ser usado com o operador de chamada segura ?.. Com o operador Elvis ?:, é possível adicionar um valor padrão quando o operador de chamada segura ?. retornar null. Ele é parecido com uma expressão if/else, mas é mais idiomático.

Se a variável não for null, a expressão antes do operador Elvis ?: vai ser executada. Se a variável for null, a expressão depois do operador Elvis ?: vai ser executada.

Um diagrama que mostra a palavra-chave "val" seguida de um bloco de nome, um sinal de igual, um bloco de variável anulável, um ponto de interrogação, um ponto, um bloco de propriedade ou método, um ponto de interrogação, dois pontos e um bloco de valor padrão.

Para modificar o programa anterior e usar o operador Elvis ?:, siga estas etapas:

  1. Remova o condicional if/else, defina a variável lengthOfName como a variável favoriteActor anulável e use o operador de chamada segura ?. para chamar a propriedade length:
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = favoriteActor?.length

    println("The number of characters in your favorite actor's name is $lengthOfName.")
}
  1. Depois da propriedade length, adicione o operador Elvis ?: seguido por um valor 0 e execute este programa:
fun main() {
    val favoriteActor: String? = "Sandra Oh"

    val lengthOfName = favoriteActor?.length ?: 0

    println("The number of characters in your favorite actor's name is $lengthOfName.")
}

A saída vai ser igual à anterior:

The number of characters in your favorite actor's name is 9.

4. Conclusão

Parabéns! Você aprendeu o que é nulidade e como usar vários operadores para gerenciá-la.

Resumo

  • Uma variável pode ser definida como null para indicar que não tem valor.
  • Variáveis não anuláveis não podem ter o valor null atribuído a elas.
  • Variáveis anuláveis podem ter o valor null atribuído a elas.
  • Para acessar métodos ou propriedades de variáveis anuláveis, você precisa usar operadores de chamada segura ?. ou de declaração não nula !!.
  • É possível usar instruções if/else com verificações de valores null para acessar variáveis anuláveis em contextos não anuláveis.
  • É possível converter uma variável anulável em um tipo não anulável com expressões if/else.
  • Com a expressão if/else ou o operador ?:, você pode fornecer um valor padrão no caso de uma variável anulável ser null.

Saiba mais