Waldo sessions now support scripting! – Learn more
App Development

Kotlin Maps: Five Essential Functions

Llamdo
Llamdo
Kotlin Maps: Five Essential Functions
October 19, 2021
5
min read

Maps, also known as associative arrays, are a core data type in any programming language. Lists and maps are probably the most common data types. Therefore, being familiar with them is a necessary step when learning a new language.

Kotlin comes with comprehensive support for maps out of the box. For starters, you can natively use Java’s collections framework, one of the most well-known and battle-tested in the business. Moreover, Kotlin adds a few extra features that make maps even more convenient to use.

In this post, I’m talking about the basics of maps. I’m going to show you the functions that you use the most when handling maps.

The Purpose of a Map in Kotlin

It’s worth understanding what a map is before using it. Essentially, a map is a collection of key and value pairs. The key is the identifier. You use it to look for specific elements in the collection. The value is the data you want to store, and it’s associated with exactly one key.

Essentially, a map is a collection of key and value pairs.

An implementation of a data type is a data structure. There are two main implementations of maps:

  • Hash tables: It uses a hash function to compute an index for every key. The pairs go in an array of buckets based on that index. The performance is linear as long as the hash function distributes keys uniformly.
  • Search tree: It uses a tree structure to store the keys. The performance isn’t as good as hash tables. However, it keeps the keys sorted according to their natural order.

Typically, you’ll use a hash table unless you need to iterate over the keys in order.

For this article, the implementation isn’t relevant because the interface doesn’t change. However, you should know that most default constructors create hash tables (LinkedHashMap in Java, for example).

Favoring Immutability for Kotlin Maps

Interestingly, the standard interface for maps in Kotlin is immutable. What does that mean? Once you initialize an instance of a map, you can’t change it anymore. Reducing the mutability of objects is a best practice. For instance, the seminal book Effective Java recommends it. Why is that? Immutable objects are easier to reason about. They’re less prone to accidental mistakes. You should use immutable objects as much as possible.

Having said that, you do have to build mutable objects sometimes. In Kotlin there’s a second interface, MutableMap, that offers write operations. Use this one whenever you need to modify the contents of your map. Once you’re done, it’s best to convert it to an immutable map to prevent further modification.

Thanks to Kotlin’s concise syntax, initializing a new map is as easy as doing the following:

 
 
val numbers: MutableMap<String, Int> = mutableMapOf(
    "one" to 1,
    "two" to 2,
    "three" to 3,
    "four" to 4,
    "five" to 5
)

Now you have a map instance. Let’s talk about the most relevant methods that you need to know to access and manipulate the data inside.

Get

The get method finds a value in the map corresponding to the given key.

It receives one argument, which is the key that you’re looking for. It returns the value associated with that key. In the case that the key isn’t present in the map, it returns null. Kotlin encourages null safety. That’s why the return type is marked clearly as a nullable type. It forces you to handle the fact that the value might be null to prevent runtime exceptions.

Null safety forces you to handle the fact that the value might be null to prevent runtime exceptions.

Let’s access a value in the map that we just defined:

 
 
val value = numbers["three"]
println("value is $value") // will print "value is 3"

Wait, what’s happening there? There’s no call to the get method. It just looks like accessing an array! Well, it doesn’t look like it, but we are calling the get method. We’re using Kotlin’s built-in operator overloading, which automatically translates the array-like access to use the get method. It makes the syntax more lightweight and easier to process as you read it. It looks like Ruby, with the significant advantage of having static typing.

Put

The put method serves two purposes:

  • It inserts a new key into the map, binding a provided value to it.
  • It replaces the value associated with an existing key with a new one.

We use the same method for both. The method receives a key and a value. If the key isn’t present in the map, it inserts it together with the value. If it already exists in the map, the key stays and the new value replaces the old one.

For insertion, the method returns null. For replacement, it returns the old value.

Let’s have a look at an example:

 
 
numbers.put("six", 6) // numbers["six"] will return 6

If you don’t need the value of the old key, you can use the indexing syntax, just like with get:

 
 
numbers["six"] = 6 // numbers["six"] will return 6

Remove

The remove method deletes a key and its associated value from the map.

It receives the key as an argument. It returns the value, which is null if the key doesn’t exist in the map.

 
 
numbers.remove("three") // numbers["three"] will return null

You use the previous call if you want to remove the key unconditionally. But, there’s a variant of this method. Instead, you can provide both a key and a value. In this case, the map deletes the pair only if the key exists and the value matches the one you provide.

 
 
numbers.remove("three", 2) // Won't remove the pair
numbers.remove("three", 3) // Will remove the pair

These three methods cover inserting, replacing, accessing, and removing elements. It’s the bare minimum you need to use maps in your code. However, the API for these interfaces is more extensive than that. There are plenty more methods to interact with maps in any way that you can imagine. I’m going to mention two more methods that you tend to use often.

Clear

The clear method removes all the items inside the map.

It doesn’t receive or return any argument. It empties the map, setting the size of the map back to zero. After using this operation, you lose access to any key or value that was there before.

 
 
numbers.clear() // numbers.size will be 0

Essentially, this is akin to creating a new map and starting from scratch.

Iterator

The iterator method is there to iterate over the contents of the map.

Concretely, it returns an iterator object. In a sense, you use an iterator to convert a map into a list. You can use this object to loop over every (key, value) pair in the map:

 
 
val iterator = numbers.iterator()
while (iterator.hasNext()) {
    val (key, value) = iterator.next()
    println("$key -> $value") // prints each pair like this "one -> 1"
}

You’ll rarely see this syntax in real code, though. Iterator is yet another operator which has an alternate syntax in Kotlin that makes the code more terse and concise:

 
 
for ((key, value) in numbers) {
    println("$key -> $value") // prints each pair like this "one -> 1"
}

This block does the same as before, but it’s significantly more readable. As you see, readability is a big factor in Kotlin.

Iterators are a base building block. On top of it, there are quite a few additional methods to go over the map and manipulate it the way you want. Some examples are convenience methods like forEach, map, or filter.

Maps Are a Core Abstraction

There’s a lot more to say about maps. Be sure to check the API reference, as there are many more methods than the ones that I’ve shown you. If you need to interact with a map in a different way, it’s likely there’s an operation that suits your needs.

Maps are ubiquitous. You’ll be using them constantly. Luckily, Kotlin got you covered. The base map class is convenient, easy to use, and spans plenty of use cases. It’s thoroughly tested and highly optimized. Don’t try to reinvent the wheel when you can reuse its functionality.

Check out Waldo if you want to learn about codeless mobile automation testing.

This post was written by Mario Fernandez. Mario develops software for a livingthen he goes home and continues thinking about software because he just can’t get enough. He’s passionate about tools and practices, such as continuous delivery. And, he’s been involved in frontend, backend, and infrastructure projects.

Automated E2E tests for your mobile app

Waldo provides the best-in-class runtime for all your mobile testing needs.
Get true E2E testing in minutes, not months.

Reproduce, capture, and share bugs fast!

Waldo Sessions helps mobile teams reproduce bugs, while compiling detailed bug reports in real time.