Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

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.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
newest oldest most voted
Notify of
Oleksii Fedorov
Guest

It would be quite fantastic also to see what happens when you want to “break” or “continue” in the nested “forEach.” In these cases, one needs to provide a label for each lambda expression block, and use this label together with “[email protected]” Here is an example: world.cities.forEach [email protected] { city -> city.streets.forEach [email protected] { street -> if (street.name.endsWith("5")) { [email protected] } if (street.name.endsWith("3")) { [email protected] } // do something smart } }

Loredana Crusoveanu
Editor

Good point, thanks for adding the info here.
We’ve covered the return, break, continue statements in a separate post: https://www.baeldung.com/kotlin-return-break-continue

Cheers.

Oleksii Fedorov
Guest

Great! I haven’t seen this other post. Thank you.

Wil Carmon
Guest
Wil Carmon

You are re-creating “callback hell”

Why not use the flatMap operator or just explicit for-each loops?

Michael
Guest
Michael

Hi Wil,

I agree with you that forEach loops should not be nested too deeply and that in most cases a flatMap is the better solution. However, the article’s purpose was to show how nested forEach loops work. We could add a paragraph to suggest the use of flatMap as preferable alternative.

Comments are closed on this article!