Aninhado forEach em Kotlin
1. Introdução
Neste breve tutorial do Kotlin, veremos o escopo do parâmetro dentro de um lambda deforEach loop.
Primeiro, definimos os dados que usaremos em nossos exemplos. Em segundo lugar, veremos como usarforEach para iterar em uma lista. Terceiro, veremos como usá-lo em loops aninhados.
2. Dados de teste
Os dados que usaremos são uma lista de países, cada um contendo uma lista de cidades, que por sua vez, contém uma lista de ruas:
class Country(val name : String, val cities : List)
class City(val name : String, val streets : List)
class World {
val streetsOfAmsterdam = listOf("Herengracht", "Prinsengracht")
val streetsOfBerlin = listOf("Unter den Linden","Tiergarten")
val streetsOfMaastricht = listOf("Grote Gracht", "Vrijthof")
val countries = listOf(
Country("Netherlands", listOf(City("Maastricht", streetsOfMaastricht),
City("Amsterdam", streetsOfAmsterdam))),
Country("Germany", listOf(City("Berlin", streetsOfBerlin))))
}
3. forEach simples
Para imprimir o nome de cada país na lista, podemos escrever o seguinte código:
fun allCountriesExplicit() {
countries.forEach { c -> println(c.name) }
}
A sintaxe acima é semelhante ao Java. No entanto, em Kotlin, se o lambda aceitar apenas um parâmetro, podemos usarit como o nome do parâmetro padrão e não precisamos nomeá-lo explicitamente:
fun allCountriesIt() {
countries.forEach { println(it.name) }
}
O acima também é equivalente a:
fun allCountriesItExplicit() {
countries.forEach { it -> println(it.name) }
}
Vale a pena notar que só podemos usarit como um nome de parâmetro implícito se não houver nenhum parâmetro explícito.
Por exemplo, o seguinte não funciona:
fun allCountriesExplicit() {
countries.forEach { c -> println(it.name) }
}
E veremos um erro em tempo de compilação:
Error:(2, 38) Kotlin: Unresolved reference: it
4. forEach aninhado
Se queremos iterar por todos os países, cidades e ruas, podemos escrever um loop aninhado:
fun allNested() {
countries.forEach {
println(it.name)
it.cities.forEach {
println(" ${it.name}")
it.streets.forEach { println(" $it") }
}
}
}
Aqui, o primeiroit se refere a um país, o segundoit a uma cidade e o terceiroit a uma rua.
No entanto, se usarmos o IntelliJ, vemos um aviso:
Implicit parameter 'it' of enclosing lambda is shadowed
Isso pode não ser um problema, mas, na linha 6, não podemos mais nos referir ao país ou cidade. If we want that, we need to explicitly name the parameter:
fun allTable() {
countries.forEach { c ->
c.cities.forEach { p ->
p.streets.forEach { println("${c.name} ${p.name} $it") }
}
}
}
5. Alternativas aos loops aninhados
Loops aninhados geralmente são difíceis de ler e devem ser evitados, se possível. Uma opção é usarflatMap():
fun allStreetsFlatMap() {
countries.flatMap { it.cities}
.flatMap { it.streets}
.forEach { println(it) }
}
No entanto, se não usarmos umflatMap aninhado, não podemos acessar o nome da cidade ou da rua na instruçãoprintln. Se quisermos ter a mesma saída do métodoallTable() acima e evitar o aninhamento, podemos adicionar duas funções de extensão:
fun City.getStreetsWithCityName() : List {
return streets.map { "$name, $it" }
.toList()
}
fun Country.getCitiesWithCountryName() : List {
return cities.flatMap { it.getStreetsWithCityName() }
.map { "$name, $it" }
}
Em seguida, use esses dois métodos com um únicoflatMap:
fun allFlatMapTable() {
countries.flatMap { it.getCitiesWithCountryName() }
.forEach { println(it) }
}
6. Conclusão
Neste breve artigo, vimos como usar o parâmetro padrãoit em Kotlin e como acessar os parâmetros de umforEach externo de dentro de um loopforEach aninhado. Finalmente, também vimos como evitar loops aninhados usandoflatMape funções de extensão.
Todos os trechos de código neste artigo podem ser encontrados em nossoGitHub repository.