O que é?
A função forEach permite percorrer uma lista, realizando alguma ação em seus elementos.
Por que aprender sobre isso?
Listas estão entre as estruturas de dados mais utilizadas em uma aplicação. Uma fatura pode ter uma lista de produtos, por exemplo. Dessa forma, lidar com listas deve ser trivial e forEach é uma maneira concisa para isso.
Características
- É escrito forEach, com "E" maiúsculo;
- É uma função presente em diversas coleções;
- Dentro do forEach, o item atual é referenciado como it.
O que saber antes de começar?
A sintaxe do forEach pode ser vista no Código 1.
fun main(args: Array<String>) {
val lista = listOf("Kotlin", "Android")
lista.forEach {
println(it)
}
}
Note que usamos chaves no forEach e não parênteses como em outras linguagens.
A variável it representa, dentro do bloco forEach, o elemento da lista que está sendo iterado.
O resultado da execução desse código é apresentado na Figura 1.
forEach na prática
Vejamos a seguir alguns exemplos práticos que introduzem conceitos importantes sobre o método forEach.
Exemplo 1 - Comparando for e forEach
No Código 2 comparamos for e forEach.
val lista = listOf("Kotlin", "Android")
for(item in lista) {
println(item)
}
lista.forEach {
println(it)
}
Note como forEach é mais conciso em relação ao for.
Exemplo 2 - Variável it
A variável it não pode ser modificada dentro de um forEach, pois é um valor (val).
Veja no Código 3 a seguir um exemplo que tenta alterar o valor de it.
val lista = listOf("Kotlin", "Android")
lista.forEach {
println(it)
it = "Não permitido"
}
Ao tentar alterar o valor de it dessa forma será lançado o erro Val cannot be reassigned como pode ser visto na Figura 2.
Exemplo 3 - Utilizando return
Caso seja necessário pular um item da lista durante o processamento, podemos usar return@forEach. Veja um exemplo no Código 4.
val lista = listOf("Kotlin", "Android")
lista.forEach {
if(it.startsWith("K")) {
return@forEach
}
println(it)
}
Nesse código it.startsWith("K") verifica se o item da lista começa com a letra K e se esse for o caso return@forEach salta o processamento para o próximo item.
Como resultado, apenas a palavra "Android" é apresentada no console como pode ser visto na Figura 3.
Exemplo 4 - Interagindo com objetos
Neste exemplo, percorremos uma lista de objetos exibindo as suas propriedades.
O objeto em questão é uma instância da classe Produto do Código 5.
data class Produto(val code: String, val valor: Double)
O código para processar essa lista ficará como mostra o Código 6.
val lista = listOf(Produto("Kotlin101", 14.99), Produto("Android101", 19.99))
lista.forEach {
println("${it.code} - ${it.valor}")
}
Dentro do bloco forEach usamos it para acessar as propriedades do objeto, como faríamos com qualquer outra instância.
Como resultado teremos o código e valor de cada produto na lista apresentado no console como exibe a Figura 4.
Exemplo 5 - Listas de listas
Para processar listas de listas podemos usar blocos forEach encadeados.
Por exemplo, suponha que tenhamos uma lista de dois itens, os quais são listas de objetos com mostra o Código 7.
val lista = listOf(
listOf(Produto("Kotlin101", 14.99), Produto("Android101", 19.99)),
listOf(Produto("Java101", 29.99), Produto("Spring101", 59.99))
)
É possível processar essa lista usando a função forEach da lista e depois em cada produto dentro dela como mostra o Código 8.
lista.forEach {
it.forEach{
println("${it.code} - ${it.valor}")
}
}
Como resultado teremos o código e valor de cada produto na lista apresentado no console como mostra a Figura 5.
Exemplo 6 - Alterando o nome da variável it
Ao lidar com blocos forEach encadeados, cuidado para não se perder quanto ao valor de it.
Por exemplo, revisitando o código anterior, tanto lista.forEach quanto it.forEach possuem uma variável interna chamada it, como mostra o Código 9.
lista.forEach { // it aqui
it.forEach{ // it aqui
println("${it.code} - ${it.valor}")
}
}
A diferença é que dentro de lista.forEach it será uma lista de objetos, como vemos no Código 10.
val lista = listOf(
listOf(Produto("Kotlin101", 14.99), Produto("Android101", 19.99)), // it de lista.forEach
...
)
Já em it.forEach it é cada objeto na lista, os quais são processados um a um, como mostra o Código 11.
val lista = listOf(
listOf(
Produto("Kotlin101", 14.99), // it de it.forEach
Produto("Android101", 19.99), // it de it.forEach
),
...
)
Para evitar confusões como qual it estamos usando, podemos mudar o nome dessa variável, atribuindo a ela melhor sentido em um dado contexto, como mostra o Código 12.
lista.forEach { // it continua com o mesmo nome aqui
it.forEach{ produto -> // it é renomeado para produto aqui
println("${produto.code} - ${produto.valor}") // produto é acessado aqui
}
}
Dessa forma removemos a ambiguidade, ao usarmos ${produto.code} - ${produto.valor} sabemos que estamos usando o objeto na lista.
Exemplo 8 - Alterando os valores das propriedades de um objeto
Dentro de um forEach podemos mudar o valor das propriedades de um objeto. Para isso vamos modificar a classe Produto para que, em lugar de val, valor seja declarado como var Veja o antes (Código 13) e o depois (Código 14).
data class Produto(val code: String, val valor: Double)
data class Produto(val code: String, var valor: Double)
Agora que a propriedade valor pode ser modificada, basta realizar essa operação dentro do forEach, como apresentado no Código 15.
lista.forEach {
it.forEach{ produto ->
produto.valor += 1.0
println("${produto.code} - ${produto.valor}")
}
}
Como resultado veremos na Figura 6, serem exibidos os valores de cada instância de Produto acrescido em 1.
Quando não utilizar forEach
Em alguns casos, usar for pode ser melhor que forEach. Vejamos alguns exemplos.
Exemplo 1 - Quando precisar utilizar continue
Cuidado, caso use continue dentro da função forEach o erro "'break' and 'continue' are only allowed inside a loop" será lançado. Lembre-se, forEach não é um laço de repetição como for, mas uma função das coleções.
O Código 16 gera o erro mencionado.
lista.forEach {
if(it.startsWith("K")) {
continue
}
println(it)
}
Como resultado da execução dele teremos a Figura 7.
No caso de continue ser necessário, talvez seja melhor usar for em lugar de forEach.
O Código 16 equivalente a esse acima, mas utilizando for em lugar de forEach pode ser visto no Código 17.
for(item in lista){
if(item.startsWith("K")) {
continue
}
println(item)
}
Note que em lugar de it, com for usamos uma variável nomeada, nesse caso, de item.
Exemplo 2 - Quando precisar utilizar break
Não existe o equivalente a break dentro da função forEach, visto que ela não é um laço de repetição. Em lugar disso, podemos usar return para encerrar a função, como mostra o Código 18.
lista.forEach {
if(it.startsWith("K")) {
return
}
println(it)
}
Como resultado nenhuma palavra será impressa, uma vez que a função será encerrada no processamento do primeiro item. Veja na Figura 8 que nada foi impresso.
No caso de break ser necessário talvez seja melhor usar for em lugar de forEach.
O Código 18 equivalente a esse acima, mas utilizando for em lugar de forEach pode ser visto no Código 19.
for(item in lista) {
if(item.startsWith("K")) {
break
}
println(item)
}
Novamente, note que em lugar de it usamos nesse exemplo uma variável chamada, nesse caso, de item.
Conclusão
No geral, a performance de forEach pode ser melhor que for. forEach é ainda preferível para processamento de objetos, quando operações como break ou continue não são necessárias. É comum ainda usarmos forEach de forma encadeada, após filter, ou outra função semelhante.