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.