Common Collection Operations

With lambda expressions in mind, let’s look at some fairly common functions available on our collection classes, most of which use a lambda expression.

Iteration

A common thing to do with a collection is to iterate over its contents. There are two ways to do that in Kotlin: forEach() and for.

forEach()

The more common approach is to use the forEach() function:

  val things = listOf("foo", "bar", "goo")

  things.forEach { println(it) }

This results in:

foo
bar
goo

In other words, forEach() iterates over each member of the collection and invokes the lambda expression for each one in turn. Our lambda expression happens to print out the value of the lambda expression’s parameter. In our case, we use the shorthand it reference to that parameter. Alternatively, we could have given it a custom name:

val things = listOf("foo", "bar", "goo")

things.forEach { thing -> println(thing) }

forEach() is a function, available for List and the other collection types. It may not look like a function, given that it lacks parentheses after the forEach name.

forEach() takes a lambda expression as a parameter. It may not look like a parameter, again due to the lack of parentheses.

In this case, this is a bit of simplified Kotlin syntax. A function that takes one lambda expression parameter can be called without the parentheses, if you are providing a literal lambda expression. So, the Kotlin shown above is equivalent to:

  val things = listOf("foo", "bar", "goo")

  things.forEach( { println(it) } )

That looks more like a conventional function call, albeit one that takes a lambda expression as a parameter. However, you will rarely see such calls written that way — the syntax that skips the unnecessary parentheses is far more common, if you are using a literal lambda expression.

The lambda expression takes a single parameter. The type of the parameter is determined by the definition of forEach() — just as a function can declare a parameter as being an Int or a String, it can declare a parameter as being a lambda expression that itself takes a certain parameter. We will see how to declare such functions later in the book. For now, take it on faith that Kotlin can figure out what the data type of the parameter to the lambda expression is… so we do not need to declare that parameter manually ourselves. Instead, we use it to refer to that parameter, in this case passing it to println().

Many functions on List, Set, etc. work this way: they take a single lambda expression as a parameter, where the lambda expression itself takes a single parameter, and we write the call without parentheses and by using it to refer to the lambda expression parameter.

Good Old-Fashioned for

forEach() is not the only way to iterate over the members of a collection. There is also for, working very similarly to for loops in other programming languages:

  val things = listOf("foo", "bar", "goo")

  for (thing in things) {
    println(thing)
  }

The parenthetical expression after the for keyword consists of a name to use for an individual member (thing) and the collection (things), separated by the in keyword. The block of code that follows can then refer to an individual member by whatever name you gave it, and that block will be executed once for each member in sequence.

(technically, this block of code is not a lambda expression, but it is fairly similar)

forEach() is somewhat more popular, as it can be used in chained expressions, as we will see in upcoming sections. However, you can use whichever of them makes you more comfortable.

filter()

filter() applies a filtering lambda expression to a collection, returning a new collection that contains the subset of items from the original collection. If the lambda expression returns true for an item, that item is included in the result collection, otherwise it is not.

  val things = listOf("foo", "bar", "goo")

  things.filter { it[1]=='o' }.forEach { println(it) }

Here we have a chained expression. We filter() the things collection, then call forEach() on the output of filter(). While this can be written on one line, you will see it more often written with each expression starting on its own line:

val things = listOf("foo", "bar", "goo")

things
  .filter { it[1]=='o' }
  .forEach { println(it) }

filter() takes a lambda expression that accepts an item from the collection and returns a Boolean, where true means that the item should be accepted and passed along, while false means that the item should be rejected. So, running this code snippet in the Klassbook yields:

foo
goo

…as bar does not have an o as the second character.

map()

filter() passes along a strict subset of the items in the collection, but the items themselves are unchanged.

map(), by contrast, passes along each item from the collection, transformed by a lambda expression that you supply:

  val things = listOf("foo", "bar", "goo")

  things
    .map { it.toUpperCase() }
    .forEach { println(it) }

Here, we map() a String to another String, converting it to uppercase:

FOO
BAR
GOO

There is no requirement for map() to return the same type as it starts with, though:

  val oneAndPrimes = listOf(1, 2, 3, 5, 7)

  oneAndPrimes
    .map { 1.0 / it }
    .forEach { println(it) }

This code snippet iterates over single-digit prime values and prints their reciprocal (1 divided by the number). Here, we use 1.0 to force a floating-point representation, and so we get Double results:

1.0
0.5
0.3333333333333333
0.2
0.14285714285714285

There are countless more of these sorts of functions, and we will see many of them as we proceed through the book. We will also explore this sort of “functional programming” approach in an upcoming chapter.

So, whereas filter() takes a collection and returns a subset of its items, map() takes a collection and returns a transformed representation of its items.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.