Kotlin Sequences: Getting Started
In this Kotlin Sequences tutorial, you’ll learn what a sequence is, its operators and when you should consider using them instead of collections. By Ricardo Costeira.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Kotlin Sequences: Getting Started
25 mins
Stateless and Stateful Operators
Intermediate operators can be:
- Stateless: They process each element independently, without needing to know about any other element.
- Stateful: They need information about other elements to process the current element.
The intermediate operators you've seen in this tutorial so far are all stateless. So, what does a stateful operator look like?
In your scratch file, just before the terminal forEach
operator, add a sortedDescending()
call, like so:
val firstHundredEvenNaturalNumbers = naturalNumbersUpToTwoHundredMillion
.take(100)
.filter { number -> number % 2 == 0 }
.sortedDescending() // add this call
.forEach { number -> println(number) }
As you can see from the scratch file output, you get the same list of numbers as before, but printed in reverse. For sortedDescending
to be able to reverse it, it had to process each element while comparing to every other element of the sequence. But how could it do that, since sequences process one element at a time?
The answer is actually quite simple, but it'll betray your confidence in sequences. Check how sortedDescending
is implemented, and you'll see that it delegates the sorting to a function called sortedWith
. In turn, if you check the implementation of sortedWith
, you'll see something like this:
public fun Sequence.sortedWith(comparator: Comparator): Sequence {
return object : Sequence { // 1
override fun iterator(): Iterator { // 2
val sortedList = this@sortedWith.toMutableList() // 3
sortedList.sortWith(comparator) // 4
return sortedList.iterator() // 5
}
}
}
Here's what the code is doing:
- It creates and returns an anonymous
object
that implements theSequence
interface. - The
object
implements theiterator()
method of theSequence
interface. - The method converts the sequence to a
MutableList
. - It then sorts the list according to the
comparator
. - Finally, it returns the list's
iterator
.
Wait, what?! It converts the sequence to a collection. That toMutableList
is a terminal operator. This intermediate operator effectively calls a terminal operator on the sequence and then outputs a new one in the end.
So, for instance, think what will happen if you call sortedDescending
on naturalNumbersUpToTwoHundredMillion
before any other operator: You'll have a MutableList
with two hundred million elements in memory! You can try it in your scratch file, but be warned that it'll take a while before you get any results.
While not all stateful operators use a MutableList
behind the curtain like sortedDescending
, they all do similar tricks to have the state needed to perform their tasks. That said, these operators can have a huge negative impact on the sequence's performance, so always be mindful of when to use them, as their impact can be strong enough for collections to be a better fit.
When to Use Sequences
After all this, you should have a rough idea of the situations where sequences might come in handy. Here's a summary of the factors that might make sequences a better fit than collections:
- Working with large datasets, applying a lot of operations.
- Using intermediate operators that avoid unnecessary work — like
take
, for instance. - Avoiding stateful operators.
- Avoiding terminal operators that convert the sequence to a collection — like
toList
, for instance.
And again, while these might point you in the right direction, don't forget: You'll never know for sure which one fits best unless you measure!
Where to Go From Here?
You can download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.
In this tutorial, you learned a lot about when to use sequences versus collections, but there's still a lot to learn about the topic.
If you want to dig deeper into Sequence
and operators, Kotlin's documentation is always a good place to start. Check out the documentation for Sequence and the list of operators.
To learn more about how they compare to collections, you can read Collections and sequences in Kotlin.
To measure the performance of an app, you'll find several methods and tools. You can check the user guide from Android Developers or the tutorial Android Memory Profiler: Getting Started.
I hope you've enjoyed this tutorial. If you have any questions, tips or comments, feel free to join the discussion below. :]