Kotlin List API — Part 1: List Filtering
A straightforward explanation of Kotlin’s List.filter method
Resources
- Kotlin Thursdays
Crowd-sourced Kotlin Tutorials - The Kotlin List API
- FP in Kotlin Part 1
Introduction
In the process of learning Kotlin I started like most people probably do: I coded like a Java developer. I wasn’t familiar with Haskell or Erlang, so functional programming was new to me. I knew Java had some functional features, but I rarely used them.
I knew of methods like filter
and sortedBy
and knew that map
and flatMap
were a thing, but the I found the official API lacking.
In this series of articles, my goal is to highlight important List
API methods that use functional programming techniques, but I may cover others as well.
In this first article, I will cover the the List.filter
method, first by implementing it myself, and then by using the actual API method.
Sample Data/Requirements
To explain how filter
works, let’s start with some sample data using Kotlin’s data class. The sample data will consist of a List
of three People
.
Sample Data
data class Person(val name:String, val age:Int)val people = listOf(
Person("Zander", 78),
Person("Fred", 12),
Person("Ali", 35)
)
Requirements
For the purposes of this example, let’s assume we need to filter a list of people to include only those people over the age of 20.
Let’s Implement Filtering (like it’s 2004)
Rather than use the actual List.filter
implementation in the Kotlin API, I’m going to implement it myself as an exercise to better understand the underlying function of filter
.
Instead of using a lot of modern language features, I’m going to pretend like it’s 2004 and implement it as simply as I can. If I were implementing this today, I’d use Lambdas, Generics and Extensions, and a lot of other language features, but for the purposes of the example I’m keeping things as simple as possible.
Let’s examine the implementation of this method.
Line 1
: The function is defined, with the input being aList<People>
and the output also being aList<People>
.Line 2
: A new list is created which will hold the return value, which will be the filtered list (note in Kotlin we’d probably use amutableList
instead of anArrayList
but remember, it’s 2004 and we’re going retro here).Line 3 and 7
: Loop through eachPerson
in the list, and assign each to theperson
variable.Line 4
: Evaluate the criteria; in this case that the person’s age is greater than 20.Line 5
: Add the person to thefilteredList
.Line 8
: Return the filteredList
Next, I’ll call the overTwenty
function passing in the people
list as a parameter:
val over20 = overTwenty(people)
The over20
list would include Zander (age 78) and Ali (age 35) but not Fred (age 12).
That works… but it’s pretty inelegant and inflexible. Time to try out Kotlin’s List.filter
method, and see how that differs.
Let’s implement it (using List.filter)
Now let’s use List.filter
rather than implementing it ourselves. The implementation looks like this:
val over20 = people.filter { it.age > 20 }
Let’s break down this call:
val over20
: a variable to hold the result of filtering; aList<People>
people
: the list being filtered; also aList<People>
.filter
: the method name.{ }
: the brackets indicate a Lambda (a function) being passed as a parameter to thefilter
method.it.age > 20
: the condition that defines how to filter elements.
- The variableit
is the implicit name of the looping variable; in other words, it’s thePerson
instance from theperson
List
.
- This function should returntrue
for elements to be included in the resultingList
, orfalse
for elements that should be excluded.
Let’s look at the advantages of List.filter
over the 2004 implementation:
- It’s much more concise (one line vs 10).
- No need to iterate the
List
usingforEach
. - No need to create the intermediate
List
and add elements to it. - Works with any type of
List
(not justList<Person>).
- Because the criteria is a (lambda) parameter, you can swap out the filter criteria and do things like
people.filter { it.age < 50 && it.name == "Fred"}
orpeople.filter { it.name == “Bill”}
. Rather than coding for a specific criteria, you can pass any criteria in as a lambda.
Certainly a great improvement.
Here’s the full code comparing the “2004” implementation vs using List.filter
in case you want to run it yourself.
Other Methods for Filtering Lists
In addition to List.filter
, there are a number of other filtering methods that have specialized purposes.
filterNot
The filterNot
method works just like the regular filter
method, except instead of filtering elements that match the condition in the lambda, it filters things that do not match.
val numbers = listOf(10,20,100,5)
val numbersNotTwenty = numbers.filterNot { it == 20}
// OUTPUT: numbersNotTwenty = [10, 100, 5]
filterIsInstance
The filterIsInstance
method filters a List
to contain only those elements that are instances of a particular class.
val manyThings = listOf("Hello", 12, "World", 15.5, null, 500)
val onlyStrings = manyThings.filterIsInstance<String>()// OUTPUT: onlyStrings = [Hello, World]
filterNotNull
The filterNotNull
method returns a List
that has all null values filtered out. Let’s do another example.
val someNulls = listOf("Hello", null, 12, null, "There")
val noNulls = someNulls.filterNotNull()
// OUTPUT: noNulls = [Hello, 12, There]
One place where this is useful is when you’re using a List that was provided to you by Java code, which includes elements which may or may not be null. In Kotlin such a List might be represented as List<String!>!
, which is a List
that may or may not null, and which contains elements that may or may not be null. (The exclamation point indicates a platform type) .
val noNullsHere = someListFromJava.filterNotNull() ?: emptyList()
The resulting noNullsHere
variable will be of type List<String>
.
filterTo, filterNotTo, filterIsInstanceTo, filterNotNullTo
In all the previous examples, the methods create a new List
and put the filtered elements in it. If you’d prefer to put the filtered items into an existing list, there’s the “To” methods, which take the “destination” List
as the first parameter of the method call.
val myExistingList = mutableListOf(1,2,3,4,5)
val listToFilter = listOf(100,300,75,6,80,7)
listToFilter.filterTo(myExistingList) { it < 10}
// OUPUT: myExistingList = [1, 2, 3, 4, 5, 6, 7]
Summary (tl;dr)
Here are the key points of how filter
method works:
- The input and output of the
filter
method is a list of a particular type:List<E>
- The
filter
method takes a lambda as a parameter that returnstrue
for elements that should be included in the returned list, orfalse
for elements that should not be included. - An example of usage is below:
val filteredList = inputList.filter { it.age > 20 }
- In addition to the normal
filter
method, there are other methods that filter things that don’t match a condition (filterNot
) , elements that are of a particular type (filterIsInstance
), elements that are not null (filterNotNull
) and some “To” methods that allow you to provide your ownList
to add to, rather than the default behavior of having aList
created for you.
Thanks for reading! I’ll see you soon for Part 2, where I will cover the List
methods any
, all
, and none.
Questions, feedback? Find me on Twitter.