Este codelab faz parte do curso de treinamento do Kotlin para programadores. Você vai aproveitar mais este curso se fizer os codelabs em sequência. Dependendo do seu conhecimento, talvez você possa passar mais rápido por algumas seções. Este curso é destinado a programadores que conhecem uma linguagem orientada a objetos e querem aprender Kotlin.
Introdução
Neste codelab, você vai conhecer vários recursos úteis do Kotlin, incluindo pares, coleções e funções de extensão.
Em vez de criar um único app de exemplo, as lições deste curso foram criadas para aumentar seu conhecimento, mas são semi-independentes umas das outras para que você possa ler as seções com que já está familiarizado. Para unificar os exemplos, muitos deles usam um tema de aquário. Se quiser conferir a história completa do aquário, acesse o curso da Udacity Treinamento do Kotlin para programadores (link em inglês).
O que você já precisa saber
- A sintaxe de funções, classes e métodos do Kotlin
- Como trabalhar com o REPL (Read-Eval-Print Loop) do Kotlin no IntelliJ IDEA
- Como criar uma nova classe no IntelliJ IDEA e executar um programa
O que você vai aprender
- Como trabalhar com pares e trios
- Saiba mais sobre as coleções
- Como definir e usar constantes
- Como escrever funções de extensão
Atividades deste laboratório
- Saiba mais sobre pares, triplas e mapas de hash no REPL
- Aprenda diferentes maneiras de organizar constantes
- Escrever uma função e uma propriedade de extensão
Nesta tarefa, você vai aprender sobre pares e triplas e como desestruturá-los. Pares e trios são classes de dados pré-criadas para 2 ou 3 itens genéricos. Isso pode ser útil, por exemplo, para uma função retornar mais de um valor.
Suponha que você tenha um List
de peixes e uma função isFreshWater()
para verificar se o peixe é de água doce ou salgada. List.partition()
retorna duas listas: uma com os itens em que a condição é true
e outra com os itens em que a condição é false
.
val twoLists = fish.partition { isFreshWater(it) }
println("freshwater: ${twoLists.first}")
println("saltwater: ${twoLists.second}")
Etapa 1: fazer alguns pares e trios
- Abra o REPL (Tools > Kotlin > Kotlin REPL).
- Crie um par, associando um equipamento à finalidade dele, e imprima os valores. Para criar um par, crie uma expressão que conecte dois valores, como duas strings, com a palavra-chave
to
e use.first
ou.second
para se referir a cada valor.
val equipment = "fish net" to "catching fish"
println("${equipment.first} used for ${equipment.second}")
⇒ fish net used for catching fish
- Crie uma tupla e imprima-a com
toString()
. Depois, converta em uma lista comtoList()
. Você cria uma tripla usandoTriple()
com três valores. Use.first
,.second
e.third
para se referir a cada valor.
val numbers = Triple(6, 9, 42)
println(numbers.toString())
println(numbers.toList())
⇒ (6, 9, 42) [6, 9, 42]
Os exemplos acima usam o mesmo tipo para todas as partes do par ou da tripla, mas isso não é obrigatório. As partes podem ser uma string, um número ou uma lista, por exemplo, ou até mesmo outro par ou tripla.
- Crie um par em que a primeira parte também seja um par.
val equipment2 = ("fish net" to "catching fish") to "equipment"
println("${equipment2.first} is ${equipment2.second}\n")
println("${equipment2.first.second}")
⇒ (fish net, catching fish) is equipment ⇒ catching fish
Etapa 2: desestruturar alguns pares e trios
Separar pares e trios em partes é chamado de desestruturação. Atribua o par ou a tripla ao número adequado de variáveis, e o Kotlin vai atribuir o valor de cada parte em ordem.
- Desestruture um par e imprima os valores.
val equipment = "fish net" to "catching fish"
val (tool, use) = equipment
println("$tool is used for $use")
⇒ fish net is used for catching fish
- Desestruture uma tupla tripla e imprima os valores.
val numbers = Triple(6, 9, 42)
val (n1, n2, n3) = numbers
println("$n1 $n2 $n3")
⇒ 6 9 42
A desestruturação de pares e trios funciona da mesma forma que com classes de dados, o que foi abordado em um codelab anterior.
Nesta tarefa, você vai aprender mais sobre coleções, incluindo listas e um novo tipo de coleção, os mapas de hash.
Etapa 1: saiba mais sobre as listas
- As listas e listas mutáveis foram apresentadas em uma lição anterior. Elas são uma estrutura de dados muito útil, então o Kotlin oferece várias funções integradas para listas. Confira esta lista parcial de funções para listas. Você pode encontrar listagens completas na documentação do Kotlin para
List
eMutableList
.
Function | Purpose |
| Adicione um item à lista mutável. |
| Remova um item de uma lista mutável. |
| Retorna uma cópia da lista com os elementos na ordem inversa. |
| Retorna |
| Retorna parte da lista, do primeiro índice até o segundo, mas sem incluí-lo. |
- Ainda trabalhando no REPL, crie uma lista de números e chame
sum()
nela. Isso soma todos os elementos.
val list = listOf(1, 5, 3, 4)
println(list.sum())
⇒ 13
- Crie uma lista de strings e some os valores dela.
val list2 = listOf("a", "bbb", "cc")
println(list2.sum())
⇒ error: none of the following functions can be called with the arguments supplied:
- Se o elemento não for algo que o
List
saiba somar diretamente, como uma string, especifique como somar usando.sumBy()
com uma função lambda, por exemplo, para somar pelo comprimento de cada string. O nome padrão de um argumento lambda éit
, e aquiit
se refere a cada elemento da lista à medida que ela é percorrida.
val list2 = listOf("a", "bbb", "cc")
println(list2.sumBy { it.length })
⇒ 6
- Há muito mais que você pode fazer com as listas. Uma maneira de conferir a funcionalidade disponível é criar uma lista no IntelliJ IDEA, adicionar o ponto e consultar a lista de preenchimento automático na dica. Isso funciona para qualquer objeto. Teste com uma lista.
- Escolha
listIterator()
na lista, percorra a lista com uma instruçãofor
e imprima todos os elementos separados por espaços.
val list2 = listOf("a", "bbb", "cc")
for (s in list2.listIterator()) {
println("$s ")
}
⇒ a bbb cc
Etapa 2: testar mapas de hash
Em Kotlin, você pode mapear quase tudo para qualquer outra coisa usando hashMapOf()
. Os mapas de hash são como uma lista de pares, em que o primeiro valor funciona como uma chave.
- Crie um mapa de hash que corresponda aos sintomas, às chaves e às doenças dos peixes, os valores.
val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- Em seguida, é possível recuperar o valor da doença com base na chave do sintoma usando
get()
ou, de forma ainda mais curta, colchetes[]
.
println(cures.get("white spots"))
⇒ Ich
println(cures["red sores"])
⇒ hole disease
- Tente especificar um sintoma que não esteja no mapa.
println(cures["scale loss"])
⇒ null
Se uma chave não estiver no mapa, tentar retornar a doença correspondente vai retornar null
. Dependendo dos dados do mapa, é comum não haver correspondência para uma possível chave. Para casos como esse, o Kotlin oferece a função getOrDefault()
.
- Tente pesquisar uma chave que não tenha correspondência usando
getOrDefault()
.
println(cures.getOrDefault("bloating", "sorry, I don't know"))
⇒ sorry, I don't know
Se você precisar fazer mais do que apenas retornar um valor, o Kotlin oferece a função getOrElse()
.
- Mude o código para usar
getOrElse()
em vez degetOrDefault()
.
println(cures.getOrElse("bloating") {"No cure for this"})
⇒ No cure for this
Em vez de retornar um valor padrão simples, qualquer código entre as chaves {}
é executado. No exemplo, else
simplesmente retorna uma string, mas pode ser tão sofisticado quanto encontrar e retornar uma página da Web com uma cura.
Assim como mutableListOf
, você também pode criar um mutableMapOf
. Um mapa mutável permite inserir e remover itens. Mutável significa capaz de mudar, e imutável significa incapaz de mudar.
- Crie um mapa de inventário que possa ser modificado, mapeando uma string de equipamento para o número de itens. Crie um com uma rede de pesca, adicione três esfregões de tanque ao inventário com
put()
e remova a rede de pesca comremove()
.
val inventory = mutableMapOf("fish net" to 1)
inventory.put("tank scrubber", 3)
println(inventory.toString())
inventory.remove("fish net")
println(inventory.toString())
⇒ {fish net=1, tank scrubber=3}{tank scrubber=3}
Nesta tarefa, você vai aprender sobre constantes em Kotlin e diferentes maneiras de organizá-las.
Etapa 1: saiba mais sobre const e val
- No REPL, tente criar uma constante numérica. No Kotlin, é possível criar constantes de nível superior e atribuir um valor a elas no momento da compilação usando
const val
.
const val rocks = 3
O valor é atribuído e não pode ser mudado, o que parece muito com a declaração de um val
regular. Então, qual é a diferença entre const val
e val
? O valor de const val
é determinado no momento da compilação, enquanto o valor de val
é determinado durante a execução do programa. Isso significa que val
pode ser atribuído por uma função no ambiente de execução.
Isso significa que val
pode receber um valor de uma função, mas const val
não.
val value1 = complexFunctionCall() // OK
const val CONSTANT1 = complexFunctionCall() // NOT ok
Além disso, const val
só funciona no nível superior e em classes singleton declaradas com object
, não com classes regulares. Você pode usar isso para criar um arquivo ou objeto singleton que contenha apenas constantes e importá-las conforme necessário.
object Constants {
const val CONSTANT2 = "object constant"
}
val foo = Constants.CONSTANT2
Etapa 2: criar um objeto complementar
O Kotlin não tem um conceito de constantes no nível da classe.
Para definir constantes dentro de uma classe, é preciso envolvê-las em objetos complementares declarados com a palavra-chave companion
. O objeto complementar é basicamente um objeto singleton dentro da classe.
- Crie uma classe com um objeto complementar que contenha uma constante de string.
class MyClass {
companion object {
const val CONSTANT3 = "constant in companion"
}
}
A diferença básica entre objetos complementares e objetos regulares é:
- Os objetos complementares são inicializados pelo construtor estático da classe contida, ou seja, são criados quando o objeto é criado.
- Objetos regulares são inicializados de forma lenta no primeiro acesso a eles, ou seja, quando são usados pela primeira vez.
Há mais informações, mas tudo o que você precisa saber por enquanto é encapsular constantes em classes em um objeto complementar.
Nesta tarefa, você vai aprender a estender o comportamento das classes. É muito comum escrever funções utilitárias para estender o comportamento de uma classe. O Kotlin oferece uma sintaxe conveniente para declarar essas funções de utilidade: as funções de extensão.
As funções de extensão permitem adicionar funções a uma classe sem precisar acessar o código-fonte. Por exemplo, você pode declará-las em um arquivo Extensions.kt que faz parte do seu pacote. Isso não modifica a classe, mas permite que você use a notação de ponto ao chamar a função em objetos dessa classe.
Etapa 1: gravar uma função de extensão
- Ainda trabalhando no REPL, escreva uma função de extensão simples,
hasSpaces()
, para verificar se uma string contém espaços. O nome da função é prefixado com a classe em que ela opera. Dentro da função,this
se refere ao objeto em que ela é chamada, eit
se refere ao iterador na chamadafind()
.
fun String.hasSpaces(): Boolean {
val found = this.find { it == ' ' }
return found != null
}
println("Does it have spaces?".hasSpaces())
⇒ true
- É possível simplificar a função
hasSpaces()
. Othis
não é explicitamente necessário, e a função pode ser reduzida a uma única expressão e retornada. Portanto, as chaves{}
ao redor dela também não são necessárias.
fun String.hasSpaces() = find { it == ' ' } != null
Etapa 2: saiba mais sobre as limitações das extensões
As funções de extensão só têm acesso à API pública da classe que estão estendendo. Variáveis private
não podem ser acessadas.
- Tente adicionar funções de extensão a uma propriedade marcada como
private
.
class AquariumPlant(val color: String, private val size: Int)
fun AquariumPlant.isRed() = color == "red" // OK
fun AquariumPlant.isBig() = size > 50 // gives error
⇒ error: cannot access 'size': it is private in 'AquariumPlant'
- Analise o código abaixo e descubra o que ele vai imprimir.
open class AquariumPlant(val color: String, private val size: Int)
class GreenLeafyPlant(size: Int) : AquariumPlant("green", size)
fun AquariumPlant.print() = println("AquariumPlant")
fun GreenLeafyPlant.print() = println("GreenLeafyPlant")
val plant = GreenLeafyPlant(size = 10)
plant.print()
println("\n")
val aquariumPlant: AquariumPlant = plant
aquariumPlant.print() // what will it print?
⇒ GreenLeafyPlant AquariumPlant
plant.print()
impressões GreenLeafyPlant
. Você pode esperar que aquariumPlant.print()
também imprima GreenLeafyPlant
, porque recebeu o valor de plant
. Mas o tipo é resolvido no momento da compilação, então AquariumPlant
é impresso.
Etapa 3: adicionar uma propriedade de extensão
Além das funções de extensão, o Kotlin também permite adicionar propriedades de extensão. Assim como nas funções de extensão, você especifica a classe que está estendendo, seguida por um ponto e pelo nome da propriedade.
- Ainda trabalhando no REPL, adicione uma propriedade de extensão
isGreen
aAquariumPlant
, que étrue
se a cor for verde.
val AquariumPlant.isGreen: Boolean
get() = color == "green"
A propriedade isGreen
pode ser acessada como uma propriedade normal. Quando acessada, o getter de isGreen
é chamado para receber o valor.
- Imprima a propriedade
isGreen
da variávelaquariumPlant
e observe o resultado.
aquariumPlant.isGreen
⇒ res4: kotlin.Boolean = true
Etapa 4: saiba mais sobre receptores anuláveis
A classe que você estende é chamada de receptor, e é possível tornar essa classe anulável. Se você fizer isso, a variável this
usada no corpo poderá ser null
. Portanto, teste isso. Você vai querer usar um receptor anulável se espera que os chamadores queiram chamar seu método de extensão em variáveis anuláveis ou se quiser fornecer um comportamento padrão quando sua função for aplicada a null
.
- Ainda trabalhando no REPL, defina um método
pull()
que receba um receptor anulável. Isso é indicado com um ponto de interrogação?
após o tipo, antes do ponto. Dentro do corpo, é possível testar sethis
não énull
usando ponto de interrogação-ponto-aplicar?.apply.
fun AquariumPlant?.pull() {
this?.apply {
println("removing $this")
}
}
val plant: AquariumPlant? = null
plant.pull()
- Nesse caso, não há saída quando você executa o programa. Como
plant
énull
, oprintln()
interno não é chamado.
As funções de extensão são muito úteis, e a maior parte da biblioteca padrão do Kotlin é implementada como funções de extensão.
Nesta lição, você aprendeu mais sobre coleções, constantes e conheceu o poder das funções e propriedades de extensão.
- Pares e trios podem ser usados para retornar mais de um valor de uma função. Exemplo:
val twoLists = fish.partition { isFreshWater(it) }
- O Kotlin tem muitas funções úteis para
List
, comoreversed()
,contains()
esubList()
. - Um
HashMap
pode ser usado para mapear chaves a valores. Por exemplo:val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- Declare constantes de tempo de compilação usando a palavra-chave
const
. Você pode colocá-los no nível superior, organizar em um objeto singleton ou colocar em um objeto complementar. - Um objeto complementar é um objeto singleton dentro de uma definição de classe, definido com a palavra-chave
companion
. - Funções e propriedades de extensão podem adicionar funcionalidades a uma classe. Exemplo:
fun String.hasSpaces() = find { it == ' ' } != null
- Um receptor anulável permite criar extensões em uma classe que pode ser
null
. O operador?.
pode ser combinado comapply
para verificarnull
antes de executar o código. Por exemplo:this?.apply { println("removing $this") }
Documentação do Kotlin
Se você quiser mais informações sobre algum tópico deste curso ou se tiver dúvidas, https://kotlinlang.org é o melhor ponto de partida.
Pair
Triple
List
MutableList
HashMap
- Objetos complementares (link em inglês)
- Extensões
- Receptor anulável
Tutoriais do Kotlin
O site https://try.kotlinlang.org inclui tutoriais avançados chamados Kotlin Koans, um interpretador baseado na Web e um conjunto completo de documentação de referência com exemplos.
Curso Udacity
Para conferir o curso da Udacity sobre esse tema, consulte Treinamento do Kotlin para programadores (link em inglês).
IntelliJ IDEA
A documentação do IntelliJ IDEA está disponível no site da JetBrains.
Esta seção lista as possíveis atividades de dever de casa para os alunos que estão fazendo este codelab como parte de um curso ministrado por um professor. Cabe ao professor fazer o seguinte:
- Atribuir o dever de casa, se necessário.
- Informar aos alunos como enviar deveres de casa.
- Atribuir nota aos deveres de casa.
Os professores podem usar essas sugestões o quanto quiserem, podendo passar os exercícios que acharem mais apropriados como dever de casa.
Se você estiver seguindo este codelab por conta própria, sinta-se à vontade para usar esses deveres de casa para testar seu conhecimento.
Responda estas perguntas
Pergunta 1
Qual das opções a seguir retorna uma cópia de uma lista?
▢ add()
▢ remove()
▢ reversed()
▢ contains()
Pergunta 2
Qual dessas funções de extensão em class AquariumPlant(val color: String, val size: Int, private val cost: Double, val leafy: Boolean)
vai gerar um erro de compilador?
▢ fun AquariumPlant.isRed() = color == "red"
▢ fun AquariumPlant.isBig() = size > 45
▢ fun AquariumPlant.isExpensive() = cost > 10.00
▢ fun AquariumPlant.isNotLeafy() = leafy == false
Pergunta 3
Qual das opções a seguir NÃO é um lugar em que você pode definir constantes com const val
?
▢ no nível superior de um arquivo
▢ em turmas regulares
▢ em objetos singleton
▢ em objetos complementares
Acesse a próxima lição:
Para ter uma visão geral do curso, incluindo links para outros codelabs, consulte "Treinamento do Kotlin para programadores: seja bem-vindo ao curso".