Codementor Events

Kotlin Collections

Published Nov 27, 2018

Learn about collections in Kotlin in this post by Marko Devcic, the author of Kotlin Quick Start Guide.

Kotlin collections were inspired by Scala and its mutable and immutable collection types. Kotlin defines its collection types in the kotlin.collections package, and there you'll see that it basically redefines types that already exist in Java. But all of them get compiled to the Java version of the same type. So you don't have to worry about any additional overhead when using them.

The Iterable interface is the basis for all collection types. It has only one method, iterator, which returns an object that can iterate over a collection:

public interface Iterable<out T> {
public operator fun iterator(): Iterator<T>
}

The Iterator object knows how to iterate over a collection. It has two methods: next, which returns the next element, and hasNext, which tells if there are any more elements in the collection:

public interface Iterator<out T> 
{
public operator fun next(): T
public operator fun hasNext(): Boolean
}

The next interface in the hierarchy is the Collection. It adds a couple more functions to the Iterable interfaces, like the size of a collection, and a way to check if an element is contained inside a collection:

public interface Collection<out E> : Iterable<E> {
public val size: Int
public fun isEmpty(): Boolean
public operator fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator<E>
public fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
}

The next in the interface hierarchy are List and Set. They both extend the Collection interface. The List is an indexed collection, and with its get method, you can obtain an element from a collection by its position:

public interface List<out E> : Collection<E> {
override val size: Int

override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator<E>

override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean

public operator fun get(index: Int): E

public fun indexOf(element: @UnsafeVariance E): Int

public fun lastIndexOf(element: @UnsafeVariance E): Int

public fun listIterator(): ListIterator<E>

public fun listIterator(index: Int): ListIterator<E>

public fun subList(fromIndex: Int, toIndex: Int): List<E>
}

The Set is an unindexed collection of unique elements, as can be seen in the following command:

public interface Set<out E> : Collection<E> {
override val size: Int

override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator<E>

override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
}

There is also the Map interface which represents a collection of key-value pairs. The Map doesn't extend the Collection and Iterable interfaces:

public interface Map<K, out V> {
public val size: Int

public fun isEmpty(): Boolean

public fun containsKey(key: K): Boolean

public fun containsValue(value: @UnsafeVariance V): Boolean

public operator fun get(key: K): V?

@SinceKotlin("1.1")
@PlatformDependent
public fun getOrDefault(key: K, defaultValue: @UnsafeVariance V): V {
return null as V
}

public val keys: Set<K>

public val values: Collection<V>

public val entries: Set<Map.Entry<K, V>>

public interface Entry<out K, out V> {
public val key: K
public val value: V
}
}

Now, all of these interfaces are present in Java, in the java.util package. But there is one big difference though: in Kotlin they are all read-only, that is, they are immutable. Kotlin differentiates between mutable and immutable collections. Each of these interfaces in Kotlin also has a mutable version, including the MutableIterator and MutableList.

The following table represents the Mutable and Immutable types with its Java representation:

Capture.PNG

The mutable interfaces extend the immutable ones and add the methods for modifying the collections. Mutable versions are considered equal to their Java counterpart.
Types that you'll be working with mostly implement the List, Set, or Map interfaces and now we'll explore them in greater detail.

Lists

Lists are collections of indexed items. Lists support insertion and retrieval of items by their position. Besides the ArrayListclass, which is probably the most used implementation, the List interface is also implemented in the LinkedList class.

The ArrayListclass is usually the one you'll use because it offers retrieval of items by position in constant time, and doesn't have the memory overhead of an additional Node object for each item which the LinkedList has. The Kotlin library provides several methods for creating instances of List interfaces. You can create the immutable instance of a List by calling the listOf function. The function accepts a vararg parameterwhich can be used to initialize the list with items:

val superHeros = listOf("Batman", "Superman", "Hulk")

There is also the arrayListOf function:

val moreSuperHeros = arrayListOf("Captain America", "Spider-Man")

If you are looking at the implementation of these methods, you might think that they are redundant, since both of them return an instance of the ArrayList class. The classes have the same name, but slightly different implementation. The one returned from the listOf function is a private class defined in the java.util.Arrays package. This is the same one you would get if you called the Arrays.asList function in Java. The arrayListOf function returns the “real” ArrayList class from the java.util package.

If you want to construct a mutable version of a List, there is the mutableListOf function:

val superHeros = mutableListOf("Thor", "Daredevil", "Iron Man")

This one can be modified as follows where the add method is defined in this version:

superHeros.add("Wolverine")

There is also the emptyList method which returns a read-only list with no items:

val empty = emptyList<String>()

Sets

A Set is a collection of unique elements. Sets have no ordering guarantees, so you cannot obtain an item by its position. There are several implementations of the Set interface in the Java standard library. Probably the most used one is the HashSet class. It can add, remove and check if an item is contained in a constant time. The LinkedHashSet class has roughly the same performance, but internally it uses a LinkedList for storing items. This comes with the added memory overhead of an additional Node object for each item it contains, but has the benefit of predictable iteration order.

When the HashSet class is iterated, it is in an unspecified order, in other words, not in the order the items were inserted. There is also the TreeSet class, which orders its items based on their natural ordering (or by the order you specify with the Comparable interface). It stores its item inside a binary tree and this gives the TreeSet a slightly worse performance than the other two implementations(logarithmic instead of constant) for adding, getting, and checking whether an item is contained.

Constructing sets can be done by calling any of the previously mentioned implementations or using the functions from the standard library. Here are a couple of examples of immutable sets:

val superHeros = setOf("Batman", "Superman")
val superHeros2 = hashSetOf("Thor", "Spider-Man")
val superHeros3 = linkedSetOf("Iron-Man", "Wolverine")
val sortedNums = sortedSetOf(3,10,12,4,9)
val empty = emptySet<String>()

If you need a mutable set, there is only one function, mutableSetOf:

val mutableSet = mutableSetOf("Hulk", "DareDevil")

Maps

Maps are collections that associate keys to values. They have similar implementations to Sets. There is the most common HashMap class, an implementation that preserves insertion order when iterating; the LinkedHashMap; and the sorted version, the TreeMap. The performance of these classes is the same as with their Sets siblings. The Kotlin standard library has these functions for initializing immutable maps:

val map = mapOf(1 to "one", 2 to "two")
val hashMap = hashMapOf(3 to "three", 4 to "four")
val linkedMap = linkedMapOf(5 to "five", 6 to "six")
val sortedMap = sortedMapOf(7 to "seven", 8 to "eight")
val empty = emptyMap<Int, String>()

All these functions accept variable arguments of a Pair type to initialize the maps. We used the to extension function to create Pairs. This is the function in the standard library:

/**
 * Creates a tuple of type [Pair] from this and [that].
 *
 * This can be useful for creating [Map] literals with less noise, for example:
 * @sample samples.collections.Maps.Instantiation.mapFromPairs
*/
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

It’s an infix function, so we can call it without the parentheses.
For constructing a mutable map, there is only one function available, the
mutableMapOf:

val mutableMap = mutableMapOf(9 to "nine", 10 to "ten")

Indexing

Lists and Maps have an option to get or set an item with the indexing operator, just as you can do with the arrays. Here’s how you would get an item from the first position of a list:

val superHeros = mutableListOf("Superman", "Batman")
val superman = superHeros[0]

And this is how you would set an item at a certain position:

superHeros[2] = "Thor"

In the case of maps, you provide the key in the square brackets and the corresponding value is returned:

val map = mutableMapOf(1 to "one", 2 to "two")
val two = map[2]

And if you want to insert something into a map, you can put the key in the square brackets and then assign a value to that key:

map[3] = "three"

Platform types and immutable collections

Kotlin features like null safety are enforced during compile time and Kotlin tries to have (great) interoperability with Java. When Java code calls your Kotlin code it is already compiled and the Kotlin compiler cannot enforce its null checks. This is the reason why the Kotlin development team introduced a concept of a Platform Type. Simply put, a Platform Type is a type defined in Java (or any other JVM language).

In the case of nullability, Kotlin code can handle them as either nullable or non-nullable. If during runtime a null value is passed to a non-nullable variable, the Kotlin runtime will throw a null pointer exception.

Immutable Kotlin Collection types are treated similarly. Remember, Kotlin doesn’t define its own Collection type, instead it uses the ones from Java. Since Java doesn’t have the same concept of mutable and immutable collections (Java has immutable collections with specific types, such as the UnmodifiableList) it is up to the Kotlin code to treat them as mutable or immutable. This is not something you usually need to worry about.

But if you are communicating in the other direction, i.e. calling Java from Kotlin, then you have to be a bit more careful. Take a look at the following Java function:

public void checkStrings(List<String> strings) {
for (String s : strings) {
// do something
}
strings.add("Item added in Java");
}

We can imagine it does some string processing or validation and then it appends an item to the list. And we can pass an immutable list from Kotlin to it, as follows:

val strings = listOf("Item added in Kotlin", "Another item added in Kotlin")
// UnsupportedOperationException thrown here
java.checkStrings(strings)

But calling this Java function will throw an UnsupportedOperationException during runtime. The reason is that the listOf function creates a specific implementation of the List interface, one that extends the AbstractList class and doesn’t override the add method from it. The base implementation of the AbstractList add method just throws the UnsupportedOperationException. In cases like this, where you know that Java code is mutating a collection, always pass some version of a mutable version from Kotlin.

Now that you know how Kotlin Collection types use the corresponding Java types, and how Kotlin enforces collections immutability during compile time, it is easy to trick the compiler and modify the immutable collection with a simple cast. Consider this example where we declare an immutable list variable, assign to it an instance of the ArrayList class and then with a simple cast we are able to add more items to it:

val list: List<String> = ArrayList()
val mutableList = (list as ArrayList).add("string")

Finally, in the same way as with mutable and immutable properties, you should favor immutable collections. This can make your code have clearer intentions. For example, if you accept an immutable list as your parameter, then a user of your API knows that you are not modifying the collection with your code.

Hope you enjoyed reading this article. To learn more about the Kotlin language, check out Kotlin Quick Start Guide, a fast-paced, hands-on guide that takes you through the core Kotlin features to get you started with developing seamless applications.

Discover and read more posts from PACKT
get started
post commentsBe the first to share your opinion
Show more replies