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
Este é o último codelab do Bootcamp do Kotlin. Neste codelab, você vai aprender sobre anotações e quebras rotuladas. Você vai revisar lambdas e funções de ordem superior, que são partes importantes do Kotlin. Você também vai aprender mais sobre funções inlining e interfaces de método abstrato único (SAM, na sigla em inglês). Por fim, você vai saber mais sobre a biblioteca padrão do Kotlin.
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 criar uma nova classe no IntelliJ IDEA e executar um programa
- Noções básicas sobre lambdas e funções de ordem superior.
O que você vai aprender
- Noções básicas de anotações
- Como usar intervalos rotulados
- Mais detalhes sobre funções de ordem superior
- Sobre interfaces de Método Abstrato Simples (SAM)
- Sobre a biblioteca padrão do Kotlin
Atividades deste laboratório
- Crie uma anotação simples.
- Use uma quebra rotulada.
- Revise as funções lambda no Kotlin.
- Usar e criar funções de ordem superior.
- Chame algumas interfaces de Método Abstrato Único.
- Usar algumas funções da biblioteca padrão do Kotlin.
As anotações são uma forma de anexar metadados ao código e não são específicas do Kotlin. As anotações são lidas pelo compilador e usadas para gerar código ou lógica. Muitos frameworks, como Ktor e Kotlinx, além do Room, usam anotações para configurar como eles são executados e interagem com seu código. É improvável que você encontre anotações até começar a usar frameworks, mas é útil saber como ler uma anotação.
Também há anotações disponíveis na biblioteca padrão do Kotlin que controlam a forma como o código é compilado. Eles são muito úteis se você estiver exportando Kotlin para código Java, mas, caso contrário, não são necessários com tanta frequência.
As anotações vêm logo antes do que é anotado, e a maioria das coisas pode ser anotada: classes, funções, métodos e até mesmo estruturas de controle. Algumas anotações podem usar argumentos.
Confira um exemplo de algumas anotações.
@file:JvmName("InteropFish")
class InteropFish {
companion object {
@JvmStatic fun interop()
}
}Isso significa que o nome exportado desse arquivo é InteropFish com a anotação JvmName, que está usando um argumento de "InteropFish".JvmName No objeto complementar, @JvmStatic informa ao Kotlin para transformar interop() em uma função estática em InteropFish.
Também é possível criar suas próprias anotações, mas isso é mais útil se você estiver escrevendo uma biblioteca que precisa de informações específicas sobre classes em tempo de execução, ou seja, reflexão.
Etapa 1: criar um novo pacote e arquivo
- Em src, crie um novo pacote,
example. - Em example, crie um novo arquivo Kotlin,
Annotations.kt.
Etapa 2: criar sua própria anotação
- Em
Annotations.kt, crie uma classePlantcom dois métodos,trim()efertilize().
class Plant {
fun trim(){}
fun fertilize(){}
}- Crie uma função que imprima todos os métodos em uma classe. Use
::classpara receber informações sobre uma classe durante a execução. UsedeclaredMemberFunctionspara receber uma lista dos métodos de uma classe. Para acessar esse recurso, importekotlin.reflect.full.*.
import kotlin.reflect.full.* // required import
class Plant {
fun trim(){}
fun fertilize(){}
}
fun testAnnotations() {
val classObj = Plant::class
for (m in classObj.declaredMemberFunctions) {
println(m.name)
}
}- Crie uma função
main()para chamar sua rotina de teste. Execute o programa e observe a saída.
fun main() {
testAnnotations()
}⇒ trim fertilize
- Crie uma anotação simples,
ImAPlant.
annotation class ImAPlantIsso não faz nada além de dizer que ele foi anotado.
- Adicione a anotação na frente da classe
Plant.
@ImAPlant class Plant{
...
}- Mude
testAnnotations()para imprimir todas as anotações de uma classe. Useannotationspara receber todas as anotações de uma classe. Execute o programa e observe o resultado.
fun testAnnotations() {
val plantObject = Plant::class
for (a in plantObject.annotations) {
println(a.annotationClass.simpleName)
}
}⇒ ImAPlant
- Mude
testAnnotations()para encontrar a anotaçãoImAPlant. UsefindAnnotation()para encontrar uma anotação específica. Execute o programa e observe o resultado.
fun testAnnotations() {
val plantObject = Plant::class
val myAnnotationObject = plantObject.findAnnotation<ImAPlant>()
println(myAnnotationObject)
}
⇒ @example.ImAPlant()
Etapa 3: criar uma anotação segmentada
As anotações podem segmentar getters ou setters. Quando isso acontecer, aplique-os com o prefixo @get: ou @set:. Isso acontece muito ao usar frameworks com anotações.
- Declare duas anotações,
OnGet, que só pode ser aplicada a getters de propriedades, eOnSet, que só pode ser aplicada a setters de propriedades. Use@Target(AnnotationTarger.PROPERTY_GETTER)ouPROPERTY_SETTERem cada um.
annotation class ImAPlant
@Target(AnnotationTarget.PROPERTY_GETTER)
annotation class OnGet
@Target(AnnotationTarget.PROPERTY_SETTER)
annotation class OnSet
@ImAPlant class Plant {
@get:OnGet
val isGrowing: Boolean = true
@set:OnSet
var needsFood: Boolean = false
}As anotações são muito úteis para criar bibliotecas que inspecionam coisas em tempo de execução e, às vezes, em tempo de compilação. No entanto, o código de aplicativo típico usa apenas anotações fornecidas por frameworks.
O Kotlin tem várias maneiras de controlar o fluxo. Você já conhece o return, que retorna de uma função para a função de inclusão. Usar um break é como return, mas para loops.
O Kotlin oferece controle adicional sobre loops com o que é chamado de interrupção rotulada. Um break qualificado com um rótulo pula para o ponto de execução logo após o loop marcado com esse rótulo. Isso é particularmente útil ao lidar com loops aninhados.
Qualquer expressão em Kotlin pode ser marcada com um rótulo. Os rótulos têm a forma de um identificador seguido pelo sinal @.
- Em
Annotations.kt, teste uma interrupção rotulada saindo de um loop interno.
fun labels() {
outerLoop@ for (i in 1..100) {
print("$i ")
for (j in 1..100) {
if (i > 10) break@outerLoop // breaks to outer loop
}
}
}
fun main() {
labels()
}- Execute o programa e observe a saída.
⇒ 1 2 3 4 5 6 7 8 9 10 11
Da mesma forma, você pode usar um continue rotulado. Em vez de sair do loop rotulado, o comando "continue" rotulado passa para a próxima iteração do loop.
Lambdas são funções anônimas, ou seja, funções sem nome. É possível atribuir esses valores a variáveis e transmiti-los como argumentos para funções e métodos. Elas são extremamente úteis.
Etapa 1: criar uma função lambda simples
- Inicie o REPL no IntelliJ IDEA: Tools > Kotlin > Kotlin REPL.
- Crie uma lambda com um argumento,
dirty: Int, que faz um cálculo, dividindodirtypor 2. Atribua a lambda a uma variável,waterFilter.
val waterFilter = { dirty: Int -> dirty / 2 }- Chame
waterFilter, transmitindo o valor 30.
waterFilter(30)⇒ res0: kotlin.Int = 15
Etapa 2: criar uma função lambda de filtro
- Ainda no REPL, crie uma classe de dados,
Fish, com uma propriedade,name.
data class Fish(val name: String)- Crie uma lista de três
Fishcom os nomes Flipper, Moby Dick e Dory.
val myFish = listOf(Fish("Flipper"), Fish("Moby Dick"), Fish("Dory"))- Adicione um filtro para verificar nomes que contenham a letra "i".
myFish.filter { it.name.contains("i")}
⇒ res3: kotlin.collections.List<Line_1.Fish> = [Fish(name=Flipper), Fish(name=Moby Dick)]
Na expressão lambda, it se refere ao elemento da lista atual, e o filtro é aplicado a cada elemento da lista por vez.
- Aplique
joinString()ao resultado, usando", "como separador.
myFish.filter { it.name.contains("i")}.joinToString(", ") { it.name }
⇒ res4: kotlin.String = Flipper, Moby Dick
A função joinToString() cria uma string unindo os nomes filtrados, separados pela string especificada. É uma das muitas funções úteis integradas à biblioteca padrão do Kotlin.
Transmitir uma lambda ou outra função como argumento para uma função cria uma função de ordem superior. O filtro acima é um exemplo simples disso. filter() é uma função, e você transmite a ela uma lambda que especifica como processar cada elemento da lista.
Escrever funções de ordem superior com lambdas de extensão é uma das partes mais avançadas da linguagem Kotlin. Leva um tempo para aprender a escrever, mas eles são muito convenientes de usar.
Etapa 1: criar uma turma
- No pacote example, crie um arquivo Kotlin chamado
Fish.kt. - Em
Fish.kt, crie uma classe de dadosFishcom uma propriedade,name.
data class Fish (var name: String)- Crie uma função
fishExamples(). EmfishExamples(), crie um peixe chamado"splashy", tudo em minúsculas.
fun fishExamples() {
val fish = Fish("splashy") // all lowercase
}- Crie uma função
main()que chamefishExamples().
fun main () {
fishExamples()
}- Compile e execute o programa clicando no triângulo verde à esquerda de
main(). Ainda não há saída.
Etapa 2: usar uma função de ordem superior
A função with() permite fazer uma ou mais referências a um objeto ou propriedade de maneira mais compacta. Uso do this. with() é uma função de ordem superior, e na lambda você especifica o que fazer com o objeto fornecido.
- Use
with()para colocar em maiúscula o nome do peixe emfishExamples(). Dentro das chaves,thisse refere ao objeto transmitido parawith().
fun fishExamples() {
val fish = Fish("splashy") // all lowercase
with (fish.name) {
this.capitalize()
}
}- Não há saída, então adicione um
println()ao redor dela. Othisé implícito e não é necessário, então você pode removê-lo.
fun fishExamples() {
val fish = Fish("splashy") // all lowercase
with (fish.name) {
println(capitalize())
}
}⇒ Splashy
Etapa 3: criar uma função de ordem superior
Por baixo dos panos, with() é uma função de ordem superior. Para ver como isso funciona, crie sua própria versão muito simplificada de with() que funcione apenas para strings.
- Em
Fish.kt, defina uma função,myWith(), que usa dois argumentos. Os argumentos são o objeto em que a operação será realizada e uma função que define a operação. A convenção para o nome do argumento com a função éblock. Nesse caso, a função não retorna nada, o que é especificado comUnit.
fun myWith(name: String, block: String.() -> Unit) {}Em myWith(), block() agora é uma função de extensão de String. A classe que está sendo estendida é geralmente chamada de objeto receptor. Portanto, name é o objeto receptor neste caso.
- No corpo de
myWith(), aplique a função transmitida,block(), ao objeto receptor,name.
fun myWith(name: String, block: String.() -> Unit) {
name.block()
}- Em
fishExamples(), substituawith()pormyWith().
fun fishExamples() {
val fish = Fish("splashy") // all lowercase
myWith (fish.name) {
println(capitalize())
}
}fish.name é o argumento de nome, e println(capitalize()) é a função de bloqueio.
- Execute o programa, e ele vai funcionar como antes.
⇒ Splashy
Etapa 4: conhecer mais extensões integradas
A lambda de extensão with() é muito útil e faz parte da biblioteca padrão do Kotlin (link em inglês). Confira alguns outros que podem ser úteis: run(), apply() e let().
A função run() é uma extensão que funciona com todos os tipos. Ela usa uma lambda como argumento e retorna o resultado da execução dela.
- Em
fishExamples(), chamerun()emfishpara receber o nome.
fish.run {
name
}Isso apenas retorna a propriedade name. Você pode atribuir isso a uma variável ou imprimir. Este não é um exemplo útil, já que você pode acessar a propriedade, mas run() pode ser útil para expressões mais complicadas.
A função apply() é semelhante a run(), mas retorna o objeto alterado a que foi aplicada em vez do resultado da lambda. Isso pode ser útil para chamar métodos em um objeto recém-criado.
- Faça uma cópia de
fishe chameapply()para definir o nome da nova cópia.
val fish2 = Fish(name = "splashy").apply {
name = "sharky"
}
println(fish2.name)
⇒ sharky
A função let() é semelhante a apply(), mas retorna uma cópia do objeto com as mudanças. Isso pode ser útil para encadear manipulações.
- Use
let()para receber o nome defish, coloque em maiúsculas, concatene outra string, receba o comprimento desse resultado, adicione 31 ao comprimento e imprima o resultado.
println(fish.let { it.name.capitalize()}
.let{it + "fish"}
.let{it.length}
.let{it + 31})⇒ 42
Neste exemplo, o tipo de objeto referenciado por it é Fish, depois String, depois String novamente e, por fim, Int.
- Imprima
fishdepois de chamarlet()e você verá que ele não mudou.
println(fish.let { it.name.capitalize()}
.let{it + "fish"}
.let{it.length}
.let{it + 31})
println(fish)⇒ 42 Fish(name=splashy)
Lambdas e funções de ordem superior são muito úteis, mas há algo que você precisa saber: lambdas são objetos. Uma expressão lambda é uma instância de uma interface Function, que é um subtipo de Object. Considere o exemplo anterior de myWith().
myWith(fish.name) {
capitalize()
}A interface Function tem um método, invoke(), que é substituído para chamar a expressão lambda. Escrito à mão, ele ficaria parecido com o código abaixo.
// actually creates an object that looks like this
myWith(fish.name, object : Function1<String, Unit> {
override fun invoke(name: String) {
name.capitalize()
}
})Normalmente, isso não é um problema, porque a criação de objetos e a chamada de funções não geram muito overhead, ou seja, tempo de memória e CPU. Mas se você estiver definindo algo como myWith(), que é usado em todos os lugares, a sobrecarga pode aumentar.
O Kotlin oferece inline como uma maneira de lidar com esse caso para reduzir a sobrecarga durante a execução, adicionando um pouco mais de trabalho para o compilador. Você aprendeu um pouco sobre inline na lição anterior sobre tipos concretos. Marcar uma função como inline significa que, sempre que ela for chamada, o compilador vai transformar o código-fonte para "inline" a função. Ou seja, o compilador vai mudar o código para substituir a lambda pelas instruções dentro dela.
Se myWith() no exemplo acima estiver marcado com inline:
inline myWith(fish.name) {
capitalize()
}ele é transformado em uma chamada direta:
// with myWith() inline, this becomes
fish.name.capitalize()É importante observar que a inclusão inline de funções grandes aumenta o tamanho do código. Portanto, é melhor usar esse recurso em funções simples que são usadas muitas vezes, como myWith(). As funções de extensão das bibliotecas que você aprendeu antes são marcadas como inline. Assim, não é preciso se preocupar com a criação de objetos extras.
Método Abstrato Único significa apenas uma interface com um método. Elas são muito comuns ao usar APIs escritas na linguagem de programação Java. Por isso, existe uma sigla para elas: SAM. Alguns exemplos são Runnable, que tem um único método abstrato, run(), e Callable, que tem um único método abstrato, call().
No Kotlin, você precisa chamar funções que usam SAMs como parâmetros o tempo todo. Teste o exemplo abaixo.
- Em example, crie uma classe Java,
JavaRun, e cole o seguinte no arquivo.
package example;
public class JavaRun {
public static void runNow(Runnable runnable) {
runnable.run();
}
}O Kotlin permite instanciar um objeto que implementa uma interface precedendo o tipo com object:. É útil para transmitir parâmetros para SAMs.
- De volta ao
Fish.kt, crie uma funçãorunExample(), que cria umRunnableusandoobject:. O objeto precisa implementarrun()imprimindo"I'm a Runnable".
fun runExample() {
val runnable = object: Runnable {
override fun run() {
println("I'm a Runnable")
}
}
}- Chame
JavaRun.runNow()com o objeto criado.
fun runExample() {
val runnable = object: Runnable {
override fun run() {
println("I'm a Runnable")
}
}
JavaRun.runNow(runnable)
}- Chame
runExample()demain()e execute o programa.
⇒ I'm a Runnable
É muito trabalho para imprimir algo, mas é um bom exemplo de como um SAM funciona. Claro, o Kotlin oferece uma maneira mais simples de fazer isso: use uma lambda no lugar do objeto para tornar esse código muito mais compacto.
- Remova o código em
runExample, mude para chamarrunNow()com uma lambda e execute o programa.
fun runExample() {
JavaRun.runNow({
println("Passing a lambda as a Runnable")
})
}
⇒ Passing a lambda as a Runnable
- Você pode deixar isso ainda mais conciso usando a sintaxe de chamada do último parâmetro e se livrar dos parênteses.
fun runExample() {
JavaRun.runNow {
println("Last parameter is a lambda as a Runnable")
}
}⇒ Last parameter is a lambda as a Runnable
Esses são os conceitos básicos de um SAM, um método abstrato único. É possível instanciar, substituir e fazer uma chamada para um SAM com uma linha de código usando o padrão:Class.singleAbstractMethod { lambda_of_override }
Esta lição revisou lambdas e abordou com mais profundidade as funções de ordem superior, que são partes importantes do Kotlin. Você também aprendeu sobre anotações e quebras rotuladas.
- Use anotações para especificar coisas ao compilador. Exemplo:
@file:JvmName("Foo") - Use interrupções rotuladas para permitir que seu código saia de loops aninhados. Exemplo:
if (i > 10) break@outerLoop // breaks to outerLoop label - Os lambdas podem ser muito eficientes quando combinados com funções de ordem superior.
- Lambdas são objetos. Para evitar a criação do objeto, marque a função com
inline. O compilador vai colocar o conteúdo da lambda diretamente no código. - Use
inlinecom cuidado, mas ele pode ajudar a reduzir o uso de recursos pelo seu programa. - SAM, Single Abstract Method, é um padrão comum, simplificado com lambdas. O padrão básico é:
Class.singleAbstractMethod { lamba_of_override } - A biblioteca padrão do Kotlin (link em inglês) oferece várias funções úteis, incluindo várias SAMs. Por isso, conheça o que ela tem.
Há muito mais em Kotlin do que foi abordado no curso, mas agora você tem o básico para começar a desenvolver seus próprios programas em Kotlin. Esperamos que você esteja animado com essa linguagem expressiva e queira criar mais funcionalidades escrevendo menos código, principalmente se você estiver migrando da linguagem de programação Java. Praticar e aprender enquanto você avança é a melhor maneira de se tornar um especialista em Kotlin. Por isso, continue explorando e aprendendo sobre Kotlin por conta própria.
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.
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.
Biblioteca padrão do Kotlin
A biblioteca padrão do Kotlin (link em inglês) oferece várias funções úteis. Antes de escrever sua própria função ou interface, sempre verifique a biblioteca padrão para saber se alguém já fez isso por você. Confira de vez em quando, porque novas funcionalidades são adicionadas com frequência.
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
Em Kotlin, SAM significa:
▢ Correspondência de argumentos segura
▢ Método de acesso simples
▢ Método abstrato único
▢ Metodologia de acesso estratégico
Pergunta 2
Qual das opções a seguir não é uma função de extensão da biblioteca padrão do Kotlin?
▢ elvis()
▢ apply()
▢ run()
▢ with()
Pergunta 3
Qual das seguintes opções não é verdadeira sobre lambdas em Kotlin?
▢ Lambdas são funções anônimas.
▢ Lambdas são objetos, a menos que sejam inlines.
▢ Lambdas consomem muitos recursos e não devem ser usados.
▢ As lambdas podem ser transmitidas para outras funções.
Pergunta 4
Os rótulos em Kotlin são indicados com um identificador seguido de:
▢ :
▢ ::
▢ @:
▢ @
Parabéns! Você concluiu o codelab do Treinamento do Kotlin para programadores.
Para ter uma visão geral do curso, incluindo links para outros codelabs, consulte "Treinamento do Kotlin para programadores: seja bem-vindo ao curso".