Este codelab faz parte do curso Treinamento do Kotlin para programadores. Você aproveitará mais o 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ê 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 desenvolvidas para aumentar seu conhecimento, mas são semi-independentes umas das outras para que você possa ler as seções que já conhece. Para conectá-los, muitos dos exemplos usam um tema de aquário. Se você quiser ver a história completa do aquário, confira o curso Bootcamp de Kotlin para programadores na Udacity.
O que você já precisa saber
- Sintaxe de funções, classes e métodos do Kotlin
- Como trabalhar com 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 triplos
- Mais sobre coleções
- Como definir e usar constantes
- Como escrever funções de extensão
Atividades do laboratório
- Saiba mais sobre pares, triplos e mapas de hash no REPL
- Aprenda maneiras diferentes de organizar constantes
- Escrever uma função e uma propriedade de extensão
Nesta tarefa, você aprenderá sobre pares e triplos e desestruturará-los. Pares e triplos são classes de dados predefinidas para dois ou três itens genéricos. Isso pode ser útil, por exemplo, para fazer uma função retornar mais de um valor.
Digamos que você tenha um List
de peixes e uma função isFreshWater()
para verificar se os peixes são de água doce ou salgada. List.partition()
retorna duas listas, uma com os itens em que a condição é true
e a outra para itens em que a condição é false
.
val twoLists = fish.partition { isFreshWater(it) }
println("freshwater: ${twoLists.first}")
println("saltwater: ${twoLists.second}")
Etapa 1: criar alguns pares e triplos
- Abra o REPL (Tools > Kotlin > Kotlin REPL).
- Crie um par, associando um equipamento ao qual ele é usado e exiba os valores. Você pode criar um par criando uma expressão conectando dois valores, como duas strings, com a palavra-chave
to
e usando.first
ou.second
para fazer referência a cada valor.
val equipment = "fish net" to "catching fish"
println("${equipment.first} used for ${equipment.second}")
⇒ fish net used for catching fish
- Crie um triplo e imprima-o com
toString()
. Em seguida, converta-o em uma lista comtoList()
. Você cria um triplo 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 peças do par ou triplo, mas isso não é obrigatório. As partes podem ser uma string, um número ou uma lista, por exemplo, até mesmo outro par ou triplo.
- Crie um par em que a primeira parte do par seja ela mesma.
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 triplos
A separação dos pares e dos triplos em suas partes é chamada de desestruturação. Atribua o par ou triplo ao número adequado de variáveis, e o Kotlin atribuirá o valor de cada parte na ordem.
- Desestruturar um par e imprimir 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
- Desestruturar um triplo e exibir os valores.
val numbers = Triple(6, 9, 42)
val (n1, n2, n3) = numbers
println("$n1 $n2 $n3")
⇒ 6 9 42
Observe que a desestruturação de pares e triplos funciona da mesma forma que em classes de dados, que eram abordadas em um codelab anterior.
Nesta tarefa, você aprenderá mais sobre coleções, incluindo listas, um novo tipo de coleção, mapas de hash.
Etapa 1: saber mais sobre listas
- Listas e listas mutáveis foram introduzidas em uma lição anterior. Eles são uma estrutura de dados muito útil, então o Kotlin fornece várias funções integradas para listas. Consulte esta lista parcial de funções para ver as listas. Você pode encontrar listagens completas na documentação do Kotlin para
List
eMutableList
.
Function | Objetivo |
| Adicione um item à lista mutável. |
| Remover um item de uma lista mutável. |
| Retorne uma cópia da lista com os elementos na ordem inversa. |
| Retorne |
| Retorne parte da lista, do primeiro índice até, mas não incluindo o segundo índice. |
- Ainda trabalhando no REPL, crie uma lista de números e chame
sum()
nela. Isso resume todos os elementos.
val list = listOf(1, 5, 3, 4)
println(list.sum())
⇒ 13
- Crie uma lista de strings e some-a.
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
List
sabe somar diretamente, como uma string, você poderá especificar como fazer isso usando.sumBy()
com uma função lambda, por exemplo, para somar pelo tamanho de cada string. O nome padrão de um argumento lambda éit
. Aqui,it
se refere a cada elemento da lista à medida que a lista é transferida.
val list2 = listOf("a", "bbb", "cc")
println(list2.sumBy { it.length })
⇒ 6
- Você pode fazer muito mais com as listas. Uma forma de ver a funcionalidade disponível é criar uma lista no IntelliJ IDEA, adicionar o ponto e, em seguida, analisar 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 exiba 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, é possível fazer praticamente o mapeamento de qualquer coisa usando hashMapOf()
. Os mapas hash são como uma lista de pares, em que o primeiro valor atua como uma chave.
- Crie um mapa de hash que corresponda aos valores, chaves e doenças dos peixes.
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 de sintomas, usando
get()
ou até mesmo colchetes menores,[]
.
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, a tentativa de retornar a doença correspondente retornará null
. Dependendo dos dados do mapa, pode ser comum não haver correspondência para uma possível chave. Para casos como esse, o Kotlin oferece a função getOrDefault()
.
- Procure uma chave sem 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 fornecerá 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 uma página da Web com uma cura e retorná-la.
Assim como mutableListOf
, você também pode criar um mutableMapOf
. Um mapa mutável permite colocar e remover itens. "Mutável" significa apenas conseguir "mudar", e "imutável" significa não conseguir mudar.
- Crie um mapa de inventário que possa ser modificado, mapeando uma string do equipamento para o número de itens. Crie-a com uma rede de peixe, adicione três tanques de peixes ao inventário com
put()
e remova a rede de peixes 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ê aprenderá sobre constantes no Kotlin e diferentes maneiras de organizá-las.
Etapa 1: saiba mais sobre a comparação entre valores constantes e constantes
- No REPL, tente criar uma constante numérica. Em Kotlin, você pode criar constantes de nível superior e atribuir um valor a elas no tempo de compilação usando
const val
.
const val rocks = 3
O valor é atribuído e não pode ser alterado. Isso soa muito parecido com a declaração de um val
normal. Qual é a diferença entre const val
e val
? O valor de const val
é determinado durante a compilação, em que o valor de val
é determinado durante a execução do programa. Isso significa que val
pode ser atribuído por uma função no momento da execução.
Isso significa que o valor de uma função pode ser atribuído a val
, 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 de nível de classe.
Para definir constantes dentro de uma classe, é necessário unir esses objetos 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 comuns é:
- Objetos complementares são inicializados a partir do construtor estático da classe contida, ou seja, eles são criados quando o objeto é criado.
- Objetos regulares são inicializados lentamente no primeiro acesso a esse objeto, ou seja, quando são usados pela primeira vez.
Há mais, mas tudo o que você precisa saber por enquanto é unir constantes em classes de um objeto complementar.
Nesta tarefa, você aprenderá a ampliar o comportamento das classes. É muito comum escrever funções utilitárias para ampliar o comportamento de uma classe. O Kotlin oferece uma sintaxe conveniente para declarar essas funções utilitárias: funções de extensão.
As funções de extensão permitem adicionar funções a uma classe existente 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: criar 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
refere-se ao objeto em que é chamado, eit
refere-se ao iteração 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, então as chaves{}
ao redor dela também não são necessárias
fun String.hasSpaces() = find { it == ' ' } != null
Etapa 2: conhecer as limitações das extensões
As funções de extensão só têm acesso à API pública da classe que estão sendo estendidas. As 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 exibirá.
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()
exibe GreenLeafyPlant
. Você também pode esperar que aquariumPlant.print()
imprima GreenLeafyPlant
, porque o valor de plant
foi atribuído a ele. Como o tipo é resolvido no momento da compilaçã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, é possível especificar a classe que será estendida, seguida por um ponto, seguido pelo nome da propriedade.
- Ainda trabalhando no REPL, adicione uma propriedade de extensão
isGreen
aAquariumPlant
, que serátrue
se a cor estiver verde.
val AquariumPlant.isGreen: Boolean
get() = color == "green"
A propriedade isGreen
pode ser acessada como uma propriedade normal. Quando acessado, o getter de isGreen
é chamado para receber o valor.
- Exiba a propriedade
isGreen
para a variávelaquariumPlant
e observe o resultado.
aquariumPlant.isGreen
⇒ res4: kotlin.Boolean = true
Etapa 4: conheça os receptores anuláveis
A classe que você estende é chamada de receiver, e é possível torná-la anulável. Se você fizer isso, a variável this
usada no corpo poderá ser null
. Portanto, teste essa variável. Use um receptor anulável se quiser que os autores das chamadas chamem seu método de extensão em variáveis anuláveis ou forneça um comportamento padrão quando a função for aplicada a null
.
- Ainda trabalhando no REPL, defina um método
pull()
que usa um receptor anulável. Isso é indicado com um ponto de interrogação?
após o tipo, antes do ponto. No corpo, é possível testar sethis
não énull
usando questionmark-dot-apply?.apply.
.
fun AquariumPlant?.pull() {
this?.apply {
println("removing $this")
}
}
val plant: AquariumPlant? = null
plant.pull()
- Nesse caso, não haverá saída ao executar o programa. Como
plant
énull
, oprintln()
interno não é chamado.
As funções de extensão são muito eficientes, 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 e de constantes e aprendeu sobre o poder das funções e propriedades de extensão.
- Pares e triplos 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 para valores. 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
. É possível colocá-los no nível superior, organizá-los em um objeto Singleton ou colocá-los em um objeto complementar. - Um objeto complementar é um objeto Singleton em uma definição de classe, definida com a palavra-chave
companion
. - As 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 que você crie extensões em uma classe que pode ser
null
. O operador?.
pode ser pareado comapply
para verificarnull
antes de executar o código. Exemplo:this?.apply { println("removing $this") }
Documentação do Kotlin
Se quiser mais informações sobre qualquer assunto deste curso ou se tiver dificuldades, https://kotlinlang.org é seu 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 (link em inglês) tem tutoriais elaborados chamados Kotlin Koans, um intérprete baseado na Web, e um conjunto completo de documentação de referência com exemplos.
Curso Udacity
Para ver o curso da Udacity sobre esse assunto, consulte Bootcamp de Kotlin para programadores (link em inglês).
IntelliJ IDEA
A documentação do IntelliJ IDEA (em inglês) pode ser encontrada no site da JetBrains.
Esta seção lista as possíveis atividades para os alunos que estão trabalhando neste codelab como parte de um curso ministrado por um instrutor. Cabe ao instrutor fazer o seguinte:
- Se necessário, atribua o dever de casa.
- Informe aos alunos como enviar o dever de casa.
- Atribua nota aos trabalhos de casa.
Os professores podem usar essas sugestões o quanto quiserem, e eles devem se sentir à vontade para passar o dever de casa como achar adequado.
Se você estiver fazendo este codelab por conta própria, use essas atividades para testar seu conhecimento.
Responda a estas perguntas
Pergunta 1
Qual das seguintes opções retorna uma cópia de uma lista?
▢ add()
▢ remove()
▢ reversed()
▢ contains()
Pergunta 2
Qual destas funções de extensão em class AquariumPlant(val color: String, val size: Int, private val cost: Double, val leafy: Boolean)
causa um erro no 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 seguintes opções 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
Vá para a próxima lição:
Para ter uma visão geral do curso, incluindo links para outros codelabs, consulte "Bootcamp de Kotlin para programadores: bem-vindo ao curso."