1. Introdução
No codelab Usar tipos de função e expressões lambda no Kotlin, você aprendeu sobre funções de ordem superior, que usam outras como parâmetros e/ou retornam uma função, como repeat()
. As funções de ordem superior são especialmente importantes para as coleções, porque elas ajudam a realizar tarefas comuns, como classificar ou filtrar itens e precisam de menos código. Agora que você tem uma base sólida para trabalhar com coleções, está na hora de recapitular o uso das funções de ordem superior.
Neste codelab, você vai aprender sobre várias funções que podem ser usadas em tipos de coleção, incluindo forEach()
, map()
, filter()
, groupBy()
, fold()
e sortedBy()
. Ao longo do processo, você vai praticar mais ainda o trabalho com expressões lambda.
Pré-requisitos
- Conhecer os tipos de função e expressões lambda.
- Conhecer a sintaxe de lambdas finais, como com a função
repeat()
. - Conhecer diferentes tipos de coleções em Kotlin, como
List
.
O que você vai aprender
- Como incorporar expressões lambda em strings.
- Como usar várias funções de ordem superior com a coleção
List
, incluindoforEach()
,map()
,filter()
,groupBy()
,fold()
esortedBy()
.
O que é necessário
- Um navegador da Web com acesso ao Playground Kotlin.
2. Função forEach() e modelos de strings com lambdas
Código inicial
Nos exemplos abaixo, você vai usar uma List
que representa o cardápio de biscoitos de uma padaria (que delícia!) e implementar as funções de ordem superior para formatar esse cardápio de maneiras diferentes.
Para começar, configure o código inicial.
- Acesse o Playground Kotlin.
- Acima da função
main()
, adicione a classeCookie
. Cada instância deCookie
representa um item no menu, com umname
, umprice
e outras informações sobre o biscoito.
class Cookie(
val name: String,
val softBaked: Boolean,
val hasFilling: Boolean,
val price: Double
)
fun main() {
}
- Abaixo da classe
Cookie
, fora da funçãomain()
, crie uma lista de biscoitos, como mostrado no exemplo abaixo. O tipo é inferido comoList<Cookie>
.
class Cookie(
val name: String,
val softBaked: Boolean,
val hasFilling: Boolean,
val price: Double
)
val cookies = listOf(
Cookie(
name = "Chocolate Chip",
softBaked = false,
hasFilling = false,
price = 1.69
),
Cookie(
name = "Banana Walnut",
softBaked = true,
hasFilling = false,
price = 1.49
),
Cookie(
name = "Vanilla Creme",
softBaked = false,
hasFilling = true,
price = 1.59
),
Cookie(
name = "Chocolate Peanut Butter",
softBaked = false,
hasFilling = true,
price = 1.49
),
Cookie(
name = "Snickerdoodle",
softBaked = true,
hasFilling = false,
price = 1.39
),
Cookie(
name = "Blueberry Tart",
softBaked = true,
hasFilling = true,
price = 1.79
),
Cookie(
name = "Sugar and Sprinkles",
softBaked = false,
hasFilling = false,
price = 1.39
)
)
fun main() {
}
Repetir uma lista com forEach()
A primeira função de ordem superior que você vai aprender a usar é a função forEach()
. A forEach()
executa a função transmitida como um parâmetro uma vez para cada item na coleção. Esse comportamento é parecido com o da função repeat()
ou de uma repetição for
. A lambda é executada para o primeiro elemento, depois para o segundo e assim por diante, até que ela seja executada para cada elemento da coleção. A assinatura do método fica assim:
forEach(action: (T) -> Unit)
forEach()
usa um único parâmetro de ação, que é uma função do tipo (T) -> Unit
.
T
corresponde ao tipo de dados incluídos na coleção. Como a lambda usa um único parâmetro, é possível omitir o nome dela e se referir ao parâmetro usando it
.
Use a função forEach()
para mostrar os itens da lista de cookies
.
- Em
main()
, chameforEach()
na lista decookies
usando a sintaxe da lambda final. Como a lambda final é o único argumento nesse ponto, é possível omitir os parênteses ao chamar a função.
fun main() {
cookies.forEach {
}
}
- No corpo da lambda, adicione uma instrução
println()
que mostrait
.
fun main() {
cookies.forEach {
println("Menu item: $it")
}
}
- Execute o código e observe a saída. O conteúdo do objeto não aparece, apenas o nome do tipo (
Cookie
) e um identificador exclusivo do objeto são mostrados.
Menu item: Cookie@5a10411 Menu item: Cookie@68de145 Menu item: Cookie@27fa135a Menu item: Cookie@46f7f36a Menu item: Cookie@421faab1 Menu item: Cookie@2b71fc7e Menu item: Cookie@5ce65a89
Incorporar expressões em strings
Ao aprender sobre os modelos de string, observamos que é possível usar o símbolo de cifrão ($
) com um nome de variável para a inserir em uma string. No entanto, isso não funciona da forma esperada para acessar propriedades quando combinado com o operador de ponto (.
).
- Na chamada para
forEach()
, modifique o corpo da lambda para inserir$it.name
na string.
cookies.forEach {
println("Menu item: $it.name")
}
- Execute o código. Isso insere o nome da classe,
Cookie
, e um identificador exclusivo para o objeto seguido por.name
. O valor da propriedadename
não é acessado.
Menu item: Cookie@5a10411.name Menu item: Cookie@68de145.name Menu item: Cookie@27fa135a.name Menu item: Cookie@46f7f36a.name Menu item: Cookie@421faab1.name Menu item: Cookie@2b71fc7e.name Menu item: Cookie@5ce65a89.name
Para acessar e incorporar as propriedades em uma string, é necessário usar uma expressão. Coloque a expressão entre chaves para fazer com que ela faça parte de um modelo de string.
A expressão lambda é colocada entre chaves. É possível acessar propriedades, realizar operações matemáticas, chamar funções, entre outras tarefas, e o valor de retorno da lambda vai ser inserido na string.
Vamos modificar o código para que o nome seja inserido na string.
- Coloque o
it.name
entre chaves para transformá-lo em uma expressão lambda.
cookies.forEach {
println("Menu item: ${it.name}")
}
- Execute o código. A saída contém o
name
de cadaCookie
.
Menu item: Chocolate Chip Menu item: Banana Walnut Menu item: Vanilla Creme Menu item: Chocolate Peanut Butter Menu item: Snickerdoodle Menu item: Blueberry Tart Menu item: Sugar and Sprinkles
3. Função map()
A função map()
permite transformar uma coleção em uma nova com o mesmo número de elementos. Por exemplo, map()
poderia transformar uma List<Cookie>
em uma List<String>
contendo apenas o name
do biscoito, desde que você informe à função map()
como criar uma String
para cada item de Cookie
.
Imagine que você está criando um app que mostra um cardápio interativo de uma padaria. Ao acessar a tela do cardápio de biscoitos, o usuário vai esperar que os dados sejam apresentados de maneira lógica, como o nome seguido pelo preço. Você pode criar uma lista de strings formatadas com os dados relevantes (nome e preço) usando a função map()
.
- Remova todo o código anterior de
main()
. Crie uma nova variável chamadafullMenu
e a defina como igual ao resultado da chamada paramap()
na lista decookies
.
val fullMenu = cookies.map {
}
- No corpo da lambda, adicione uma string formatada para incluir o
name
e oprice
deit
.
val fullMenu = cookies.map {
"${it.name} - $${it.price}"
}
- Mostre o conteúdo de
fullMenu
. Para isso, useforEach()
. A coleçãofullMenu
retornada demap()
tem o tipoList<String>
, em vez deList<Cookie>
. CadaCookie
emcookies
corresponde a umaString
emfullMenu
.
println("Full menu:")
fullMenu.forEach {
println(it)
}
- Execute o código. A saída corresponde ao conteúdo da lista de
fullMenu
.
Full menu: Chocolate Chip - $1.69 Banana Walnut - $1.49 Vanilla Creme - $1.59 Chocolate Peanut Butter - $1.49 Snickerdoodle - $1.39 Blueberry Tart - $1.79 Sugar and Sprinkles - $1.39
4. Função filter()
A função filter()
permite criar um subconjunto de itens de uma coleção. Por exemplo, você poderia usar filter()
em uma lista de números para criar uma nova lista contendo apenas números divisíveis por 2.
Enquanto o resultado da função map()
sempre gera uma coleção de mesmo tamanho, filter()
gera uma coleção do mesmo tamanho ou menor que a original. Ao contrário de map()
, a coleção resultante contém o mesmo tipo de dados. Portanto, filtrar uma List<Cookie>
resulta em outra List<Cookie>
.
Semelhante a map()
e forEach()
, filter()
usa uma única expressão lambda como parâmetro. A lambda tem um único parâmetro que representa cada item na coleção e retorna um valor Boolean
.
Em cada item da coleção:
- Se o resultado da expressão lambda for
true
, o item vai ser incluído na nova coleção. - Se o resultado for
false
, o item não vai ser incluído na nova coleção.
Isso é útil caso você queira formar um subconjunto de dados no seu app. Por exemplo, suponha que a padaria queira destacar biscoitos em uma parte separada do cardápio. Nesse caso, você pode filter()
(filtrar) a lista de cookies
antes de mostrar os itens.
- Em
main()
, crie uma nova variável com o nomesoftBakedMenu
e a defina como igual ao resultado da chamada defilter()
na lista decookies
.
val softBakedMenu = cookies.filter {
}
- No corpo da lambda, adicione uma expressão booleana para verificar se a propriedade
softBaked
do biscoito é igual atrue
. ComosoftBaked
é umBoolean
, o corpo da lambda só precisa conterit.softBaked
.
val softBakedMenu = cookies.filter {
it.softBaked
}
- Mostre o conteúdo de
softBakedMenu
usandoforEach()
.
println("Soft cookies:")
softBakedMenu.forEach {
println("${it.name} - $${it.price}")
}
- Execute o código. O menu é mostrado como antes, mas incluindo somente os biscoitos.
... Soft cookies: Banana Walnut - $1.49 Snickerdoodle - $1.39 Blueberry Tart - $1.79
5. Função groupBy()
A função groupBy()
pode ser usada para transformar uma lista em um mapa. Cada valor de retorno exclusivo dessa função se transforma em uma chave no mapa gerado. Os valores de cada chave correspondem a todos os itens da coleção que produziu o valor de retorno exclusivo.
O tipo de dados das chaves é o mesmo que o tipo de retorno da função transmitida para groupBy()
. O tipo de dados dos valores é uma lista de itens derivados da lista original.
Ela é difícil de explicar; então vamos começar com um exemplo simples. Na mesma lista usada anteriormente, agrupe os números em ímpares ou pares.
Para descobrir se um número é ímpar ou par, divida-o por 2
e confira se o resto da divisão é 0
ou 1
. Se o resultado for 0
, o número é par. Caso contrário, se o resto da divisão for 1
, o número é ímpar.
Para fazer isso, você pode usar o operador de módulo (%
), que divide o número ao lado esquerdo de uma expressão pelo valor à direita.
Em vez de retornar o resultado da divisão, como o operador de divisão (/
), o operador de módulo retorna o resto. Isso é útil para verificar se um número é par ou ímpar.
A função groupBy()
é chamada usando esta expressão lambda: { it % 2 }
.
O mapa resultante tem duas chaves: 0
e 1
. Cada chave tem um valor do tipo List<Int>
. A lista da chave 0
contém todos os números pares e a lista da chave 1
, todos os números ímpares.
Um exemplo de caso de uso real para essa função seria um app de fotos que agrupa as imagens de acordo com o conteúdo ou local em que elas foram tiradas. No cardápio da padaria, vamos dividir os biscoitos entre macios ou crocantes.
Use a função groupBy()
para agrupar o cardápio de acordo com a propriedade softBaked
.
- Remova a chamada para a função
filter()
da etapa anterior.
Código a ser removido
val softBakedMenu = cookies.filter {
it.softBaked
}
println("Soft cookies:")
softBakedMenu.forEach {
println("${it.name} - $${it.price}")
}
- Chame
groupBy()
na lista decookies
e armazene o resultado em uma variável com o nomegroupedMenu
.
val groupedMenu = cookies.groupBy {}
- Transmita uma expressão lambda que retorne
it.softBaked
. O tipo de retorno vai serMap<Boolean, List<Cookie>>
.
val groupedMenu = cookies.groupBy { it.softBaked }
- Crie uma variável
softBakedMenu
contendo o valor degroupedMenu[true]
e uma variávelcrunchyMenu
contendo o valor degroupedMenu[false]
. Como o resultado da assinatura deMap
é anulável, é possível usar o operador Elvis (?:
) para retornar uma lista vazia.
val softBakedMenu = groupedMenu[true] ?: listOf()
val crunchyMenu = groupedMenu[false] ?: listOf()
- Adicione o código para mostrar o cardápio de biscoitos macios, seguido pelo cardápio de biscoitos crocantes.
println("Soft cookies:")
softBakedMenu.forEach {
println("${it.name} - $${it.price}")
}
println("Crunchy cookies:")
crunchyMenu.forEach {
println("${it.name} - $${it.price}")
}
- Execute o código. Usando a função
groupBy()
, divida a lista em duas, de acordo com o valor das propriedades.
... Soft cookies: Banana Walnut - $1.49 Snickerdoodle - $1.39 Blueberry Tart - $1.79 Crunchy cookies: Chocolate Chip - $1.69 Vanilla Creme - $1.59 Chocolate Peanut Butter - $1.49 Sugar and Sprinkles - $1.39
6. Função fold()
A função fold()
é usada para gerar um valor único de uma coleção. Normalmente, esse recurso é usado para calcular o preço total, somar todos os elementos de uma lista ou encontrar um valor médio.
A função fold()
usa dois parâmetros:
- Um valor inicial. O tipo de dados é inferido ao chamar a função, ou seja, o valor inicial de
0
é inferido comoInt
. - Uma expressão lambda que retorna um valor com o mesmo tipo do valor inicial.
Além disso, a expressão lambda tem dois parâmetros:
- O primeiro é conhecido como acumulador. Ele tem o mesmo tipo de dados que o valor inicial. Você pode considerá-lo como um valor total. Cada vez que a expressão lambda é chamada, o acumulador é igual ao valor de retorno da chamada anterior.
- O segundo parâmetro é do mesmo tipo de cada elemento da coleção.
Assim como ocorre com outras funções apresentadas, a expressão lambda é chamada para cada elemento em uma coleção. Você pode usar fold()
como uma forma concisa de somar todos os elementos.
Vamos usar fold()
para calcular o preço total de todos os biscoitos.
- Em
main()
, crie uma nova variável com o nometotalPrice
e a defina como igual ao resultado da chamada defold()
na lista decookies
. Transmita0.0
como valor inicial. O tipo é inferido comoDouble
.
val totalPrice = cookies.fold(0.0) {
}
- Você precisa especificar os dois parâmetros para a expressão lambda. Use
total
para o acumulador ecookie
para o elemento da coleção. Use uma seta (->
) depois da lista de parâmetros.
val totalPrice = cookies.fold(0.0) {total, cookie ->
}
- No corpo da lambda, calcule a soma de
total
ecookie.price
. O resultado vai ser inferido como o valor de retorno e transmitido paratotal
na próxima vez que a lambda for chamada.
val totalPrice = cookies.fold(0.0) {total, cookie ->
total + cookie.price
}
- Mostre o valor de
totalPrice
, formatado como uma string para facilitar a leitura.
println("Total price: $${totalPrice}")
- Execute o código. O resultado vai ser igual à soma dos preços na lista de
cookies
.
... Total price: $10.83
7. Função sortedBy()
Ao começar a estudar sobre coleções, você aprendeu que a função sort()
podia ser usada para classificar elementos. No entanto, isso não funciona em uma coleção de objetos Cookie
. Como a classe Cookie
tem várias propriedades, o Kotlin não consegue saber se você quer classificar name
, price
ou alguma outra propriedade.
As coleções em Kotlin têm uma função sortedBy()
para esses casos. sortedBy()
permite especificar uma lambda que retorna a propriedade que você quer classificar. Por exemplo, se você quiser classificar os itens por price
, a lambda vai retornar it.price
. Desde que o tipo de dados do valor esteja em uma ordem de classificação lógica, ou seja, com strings em ordem alfabética e valores numéricos em ordem crescente, os dados vão ser classificados como um conjunto desse tipo.
Use sortedBy()
para ordenar a lista de biscoitos em ordem alfabética.
- Em
main()
, depois do código existente, adicione uma nova variável com o nomealphabeticalMenu
e a configure como igual a uma chamada desortedBy()
na lista decookies
.
val alphabeticalMenu = cookies.sortedBy {
}
- Na expressão lambda, retorne
it.name
. A lista resultante ainda vai ser do tipoList<Cookie>
, mas ela estará classificada de acordo com oname
.
val alphabeticalMenu = cookies.sortedBy {
it.name
}
- Mostre os nomes dos biscoitos em
alphabeticalMenu
. Você pode usarforEach()
para mostrar um nome em cada linha.
println("Alphabetical menu:")
alphabeticalMenu.forEach {
println(it.name)
}
- Execute o código. Os nomes dos biscoitos vão aparecer em ordem alfabética.
... Alphabetical menu: Banana Walnut Blueberry Tart Chocolate Chip Chocolate Peanut Butter Snickerdoodle Sugar and Sprinkles Vanilla Creme
8. Conclusão
Parabéns! Você conferiu vários exemplos de como funções de ordem superior podem ser usadas com coleções. Com elas, é possível executar operações comuns, como classificar e filtrar, em uma única linha de código, deixando seus programas mais concisos e expressivos.
Resumo
- É possível repetir cada elemento de uma coleção usando
forEach()
. - As expressões podem ser inseridas em strings.
map()
é usada para formatar os itens de uma coleção, geralmente como um conjunto de outro tipo de dados.filter()
pode gerar um subconjunto de uma coleção.groupBy()
divide uma coleção com base no valor de retorno de uma função.fold()
transforma uma coleção em um valor único.sortedBy()
é usada para classificar uma coleção de acordo com a propriedade especificada.