1. Overview
Kotlin builds on top of the Java Collection framework using extension methods. This dramatically improves usability and readability without the need of third-party dependencies such as Apache Commons or Guava.
In this tutorial, we’ll focus on sorting in Kotlin. Also, we’ll use the kotlin.comparisons package to implement complex ordering rules.
2. Sorting a Collection
Kotlin provides multiple utilities to make the process of sorting collections easier. Let’s explore several of these methods.
2.1. Sort
The simplest way to sort a collection is to call the sort method. This method will use the natural order of the elements. Also, it’ll order in ascending direction by default, so ‘a’ is before ‘b’ and ‘1’ is before ‘2’:
val sortedValues = mutableListOf(1, 2, 7, 6, 5, 6) sortedValues.sort() println(sortedValues)
And the result of the above code is:
[1, 2, 5, 6, 6, 7]
It’s important to note that we’ve used a mutable collection. The reason is that the sort method will sort in-place. If we want the result returned as a new list, then we just need to use the sorted method instead.
Furthermore, we can use the sortDescending or reverse methods for sorting in a descending order.
2.2. SortBy
If we need to sort by specific properties of a given object, we can use sortBy. The sortBy method allows us to pass a selector function as an argument. The selector function will receive the object and should return the value on which we’d like to sort:
val sortedValues = mutableListOf(1 to "a", 2 to "b", 7 to "c", 6 to "d", 5 to "c", 6 to "e") sortedValues.sortBy { it.second } println(sortedValues)
And the result of the above code is:
[(1, a), (2, b), (7, c), (5, c), (6, d), (6, e)]
Again, the collection needs to be mutable because the sortBy method will sort in-place. If we want the result returned as a new list, then we need to use the sortedBy method instead of the sortBy method.
Like before, for descending order, we can use the sortByDescending or reverse methods.
2.3. SortWith
For a more advanced usage (to combine multiple rules, for example), we can use the sortWith method.
We can pass a Comparator object as an argument. In Kotlin we have multiple ways to create Comparator objects and we will cover that in the next section:
val sortedValues = mutableListOf(1 to "a", 2 to "b", 7 to "c", 6 to "d", 5 to "c", 6 to "e") sortedValues.sortWith(compareBy({it.second}, {it.first})) println(sortedValues)
And the result of the above code is that they are sorted by letter and then by number:
[(1, a), (2, b), (5, c), (7, c), (6, d), (6, e)]
Because the sortWith will do the sorting in-place, we need to use a mutable collection. If we want the result returned as a new collection then we need to use the sortedWith method instead of the sortWith method.
For descending order, we can use the reverse method or alternatively define the right Comparator.
3. Comparison
Kotlin contains a very useful package to build a Comparator – kotlin.comparisons. In the following sections, we’ll discuss:
- Comparator creation
- Handling of null values
- Reversing the order
- Comparator rules extension
3.1. Comparator Creation
In order to simplify the creation of our Comparator, Kotlin brings many factory methods to make our code more expressive.
The simplest Comparator factory available is naturalOrder(). No arguments are needed and the order is ascending by default:
val ascComparator = naturalOrder<Long>()
For objects with multiple properties, we can use the compareBy method. As arguments, we give a variable number of functions (sorting rules) that will each return a Comparable object. Then, those functions will be called sequentially until the resulting Comparable object evaluates as not equal or until all functions are called.
In the next example, it.first value is used for comparisons and, only when values are equal, it.second will be called to break the tie:
val complexComparator = compareBy<Pair<Int, String?>>({it.first}, {it.second})
Feel free to explore kotlin.comparisons to discover all the available factories.
3.2. Handling of null Values
A simple way to improve our Comparator with null value handling is to use the nullsFirst or nullsLast methods. These methods will sort null values in first or last place respectively:
val sortedValues = mutableListOf(1 to "a", 2 to null, 7 to "c", 6 to "d", 5 to "c", 6 to "e") sortedValues.sortWith(nullsLast(compareBy { it.second })) println(sortedValues)
The result of the above code will be:
[(1, a), (7, c), (5, c), (6, d), (6, e), (2, null)]
We can see that the last value in the resulting collection is the one with null value.
3.3. Reversing the Order
To reverse the order, we can use the reverseOrder method or the reversed method. The former method has no arguments and returns a descending order. The latter method can be applied on a Comparator object and it will return its reversed Comparator object.
To build a Comparator using descending natural order we can do:
reverseOrder()
3.4. Comparator Rules Extension
Comparator objects can be combined or extended with additional sorting rules via the then methods available in kotlin.comparable package.
Only when the first comparator evaluates to equal, the second comparator will then be used.
Our list of students contains an age and a name for each individual. We want them sorted from youngest to oldest and, when they are of the same age, descending based on the name:
val students = mutableListOf(21 to "Helen", 21 to "Tom", 20 to "Jim") val ageComparator = compareBy<Pair<Int, String?>> {it.first} val ageAndNameComparator = ageComparator.thenByDescending {it.second} println(students.sortedWith(ageAndNameComparator))
The result of the above code will be:
[(20, Jim), (21, Tom), (21, Helen)]
4. Conclusion
In this quick tutorial, we saw how to use the sort, sortBy and sortWith methods in order to sort collections in Kotlin.
Later, we also used kotlin.comparisons package to create Comparator objects and to enhance them with additional sorting rules.
The implementation of all of these examples and snippets can be found over on GitHub.