Reviewing Kotlin Data Structures

Learn basic data structures and their properties in Kotlin.

There are three important groups of data structures we should get familiar with in Kotlin:

  • Lists
  • Sets
  • Maps

We’ll cover each briefly, then discuss some other topics related to data structures, such as mutability and tuples.

Lists

A list represents an ordered collection of elements of the same type. To declare a list in Kotlin, we use the listOf() function:

 val hobbits = listOf("Frodo", "Sam", "Pippin", "Merry")

Note that we didn’t specify the type of the list. The reason is that the type inference can also be used when constructing collections in Kotlin, the same as when initializing variables. If we want to provide the type of the list, we similarly do that for defining arguments for a function, as shown below:

 val hobbits: List<String> = listOf("Frodo", "Sam", "Pippin", "Merry")

To access an element in the list at a particular index, we use square brackets:

println(hobbits[1])

Let’s try this by ourselves:

Press + to interact
fun main() {
lists()
}
fun lists() {
val hobbits = listOf("Frodo", "Sam", "Pippin", "Merry")
println(hobbits[1])
// Won't compile
// hobbits[0] = "Bilbo" // Unresolved reference
val editableHobbits = mutableListOf("Frodo", "Sam", "Pippin", "Merry")
editableHobbits.add("Bilbo")
println(editableHobbits)
}

Sets

A set represents a collection of unique elements. Looking for the presence of an element in a set is much faster than looking it up in a list. But, unlike lists, sets don’t provide indexes access.

Let’s create a set of football World Cup champions until after 1994:

Press + to interact
val footballChampions = setOf("France", "Germany", "Spain", "Italy",
"Brazil", "France", "Brazil", "Germany")
println(footballChampions) // [France, Germany, Spain, Italy, Brazil]

We can see that each country exists in a set exactly once. To check whether an element is in a Set collection, we can use the in operator:

println("Israel" in footballChampions) 
println("Italy" in footballChampions)

Note that although sets, in general, do not guarantee the order of elements, the current implementation of a setOf() function returns LinkedHashSet, which preserves the insertion order as France appears first in the output since it was the first country in the input.

Let’s see the output by executing the code:

Press + to interact
fun main() {
sets()
}
fun sets() {
val footballChampions = setOf("France", "Germany", "Spain", "Italy", "Brazil", "France", "Brazil", "Germany")
println(footballChampions)
println("Israel" in footballChampions) // false
println("Italy" in footballChampions) // true
}

Maps

A map is a collection of key-value pairs in which keys are unique. In the following code, the to keyword creates a pair of two elements. In fact, this is not a real keyword but a special function. Let’s create a map of some of the Batman movies and the actors that played Bruce Wayne in them:

Press + to interact
val movieBatmans = mapOf(
"Batman Returns" to "Michael Keaton",
"Batman Forever" to "Val Kilmer",
"Batman & Robin" to "George Clooney"
)
println(movieBatmans)

To access a value by its key, we use square brackets and provide the key:

 println(movieBatmans["Batman Returns"])

Those data structures also support checking that an element doesn’t exist:

println(" Batman Begins " !in movieBatmans)

Mutability

All of the data structures we have discussed so far are immutable or, more correctly, read-only. There are no methods to add new elements to a list we create with the listOf() function, and we also cannot replace any element:

hobbits[0] = "Bilbo " // Unresolved reference!

Immutable data structures are great for writing concurrent code. But, sometimes, we still need a collection we can modify. In order to do that, we can use the mutable counterparts of the collection functions:

val editableHobbits = mutableListOf("Frodo", "Sam", "Pippin", "Merry")

editableHobbits.add("Bilbo")

Editable collection types have functions such as add() that allow us to modify or, in other words, mutate them.

Alternative implementations for collections

If you have worked with JVM before, you may know that there are other implementations of sets and maps. For example, TreeMap stores the keys in sorted order. Here’s how you can instantiate them in Kotlin:

Press + to interact
// Mutable map that is sorted by its keys
val treeMap = java.util.TreeMap(
mapOf(
"Practical Pig" to "bricks",
"Fifer" to "straw",
"Fiddler" to "sticks"
)
)
println(treeMap.keys)

Note that the names of the Three Little Pigs are ordered alphabetically. Let’s see the output by executing the code:

Press + to interact
fun main() {
maps()
}
fun maps() {
val movieBatmans = mapOf(
"Batman Returns" to "Michael Keaton",
"Batman Forever" to "Val Kilmer",
"Batman & Robin" to "George Clooney"
)
println(movieBatmans["Batman Returns"])
println("Batman Begins" !in movieBatmans)
// Mutable map that is sorted by its keys
val treeMap = TreeMap(
mapOf(
"Practical Pig" to "bricks",
"Fifer" to "straw",
"Fiddler" to "sticks"
)
)
println(treeMap.keys)
}

Arrays

In Java, arrays have a special syntax that uses square brackets. For example, an array of strings is declared String[], while a list of strings is declared as List<String>. An element in a Java array is accessed using square brackets, while an element in a list is accessed using the get() method.

Press + to interact
Array representation in Java
Array representation in Java

To get the number of elements in an array in Java, we use the length() method, and to do the same with a collection, we use the size() method. This is part of Java’s legacy and its attempts to resemble C++.

In Kotlin, array syntax is consistent with other types of collections. An array of strings is declared as Array<String>:

 val musketeers: Array<String> = arrayOf("Athos", "Porthos", "Aramis")

This is the first time we see angle brackets in Kotlin code. Similar to Java or TypeScript, the type between them is called a type argument. It indicates that this array contains strings.

If we already have a collection and would like to convert it into an array, we’ll use the toTypedArray function:

listOf(1, 2, 3, 5).toTypedArray()

In terms of its abilities, a Kotlin array is very similar to a list. For example, to get the number of elements in a Kotlin array, we use the same size property as other collections.

So, when would we need to use arrays, then? One example is accepting arguments in the main function. Previously, we’ve seen only main functions without arguments, but sometimes we want to pass them from a command line. Here’s an example of a main function that accepts arguments from a command line and prints all of them, separated by commas:

Press + to interact
fun main(args: Array<String>) {
println(args.joinToString(", "))
}

Let’s just try these by ourselves:

Press + to interact
fun main(args: Array<String>) {
arrays()
println(args.joinToString(", "))
}
fun arrays() {
val musketeers: Array<String> = arrayOf("Athos", "Porthos", "Aramis")
println(musketeers[0])
println(musketeers[1])
println(musketeers[2])
listOf(1, 2, 3, 5).toTypedArray()
}