1. Introduction

In this short Kotlin tutorial, we’ll look at the parameter scope inside a forEach loop’s lambda.

First, we define the data which we’ll use in our examples. Second, we’ll see how to use forEach to iterate over a list. Third, we’ll look at how to use it in nested loops.

2. Test Data

The data we’ll use is a list of countries, each containing a list of cities, which in turn, contain a list of streets:

class Country(val name : String, val cities : List<City>)

class City(val name : String, val streets : List<String>)

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. Simple forEach

To print the name of each country in the list, we can write the following code:

fun allCountriesExplicit() { 
    countries.forEach { c -> println(c.name) } 
}

The above syntax is similar to Java. However, in Kotlin, if the lambda accepts only one parameter, we can use it as the default parameter name and do not need to name it explicitly:

fun allCountriesIt() { 
    countries.forEach { println(it.name) } 
}

The above is also equivalent to:

fun allCountriesItExplicit() {
    countries.forEach { it -> println(it.name) }
}

It’s worthwhile to note that we can only use it as an implicit parameter name if there’s no explicit parameter.

For example, the following doesn’t work:

fun allCountriesExplicit() { 
    countries.forEach { c -> println(it.name) } 
}

And we’ll see an error at compile-time:

Error:(2, 38) Kotlin: Unresolved reference: it

4. Nested forEach

If we want to iterate over all countries, cities, and streets, we can write a nested loop:

fun allNested() {
    countries.forEach {
        println(it.name)
        it.cities.forEach {
            println(" ${it.name}")
            it.streets.forEach { println("  $it") }
        }
    }
}

Here, the first it refers to a country, the second it to a city and the third it to a street.

However, if we use IntelliJ, we see a warning:

Implicit parameter 'it' of enclosing lambda is shadowed

This might not be a problem, but, in line 6 we cannot refer to the country or city anymore. 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. Alternatives to Nested Loops

Nested loops are generally difficult to read and should be avoided if possible. One option is to use flatMap():

fun allStreetsFlatMap() {
    countries.flatMap { it.cities}
      .flatMap { it.streets}
      .forEach { println(it) }
}

However, if we don’t use a nested flatMap, we can’t access the city or street name in the println statement. If we want to have the same output as in the above method allTable() and avoid nesting, we could add two extension functions:

fun City.getStreetsWithCityName() : List<String> {
    return streets.map { "$name, $it" }
      .toList()
}

fun Country.getCitiesWithCountryName() : List<String> {
    return cities.flatMap { it.getStreetsWithCityName() }
      .map { "$name, $it" }
}

And then use these two methods with a single flatMap:

fun allFlatMapTable() {
    countries.flatMap { it.getCitiesWithCountryName() }
      .forEach { println(it) }
}

6. Conclusion

In this short article, we saw how to use the default parameter it in Kotlin and how to access the parameters of an outer forEach from within a nested forEach loop. Finally, we also looked at how to avoid nested loops using flatMap and extension functions.

All the code snippets in this article can be found in our GitHub repository.

Comments are closed on this article!