Aninhado forEach em Kotlin

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.