🚗 · Basic Collections: Introduction

5 min read · Updated on by

Read on to learn why Java collections are extremely problematic, how Kotlin takes a different approach, and a description of all the different methods to create collections — construction from elements, builder functions, concrete type constructors/initializer functions, copying and invoking functions on other collections.

The Java status quo

One of the biggest failures of Java collections is that they are mutable by default — for example, java.util.List describes a mutable list. This is a pretty serious problem, because it breaks the ‘L’ (which stands for Liskov Substitution Principle) in SOLID, namely that subclasses are always usable in place of their superclasses.

To see how, think about how you would implement an immutable list in Java. Naturally, you’d want it to implement the java.util.List interface, because that’s what everyone uses, but that interface describes a mutable list — it defines methods such as addremove etc. In order to conform to this interface, you have no choice but to implement these by throwing an exception, which is exactly what e.g. Guava does in its ImmutableList implementation.

Unfortunately, what this means is that your implementation cannot be used safely wherever it’s parent (e.g. List) can. You can call add on List, but you can never be sure it won’t throw an exception. I can pass a Guava ImmutableList anywhere a List is expected with no complaint from the compiler, and introduce a runtime error just like that. And as we’ve said many times before, runtime errors are your worst enemy!

What’s even worse is that Java actively encourages you to do this, which is just beyond comprehension. So, to sum it up, in Java:

  • When implementing an immutable list, we have the choice of either implementing List, thereby breaking the Liskov Substitution Principle, or not implementing List, thereby making our implementation practically unusable
  • We have no way to enforce immutability at the declaration site (e.g. when defining a method parameter) without tying our declaration to a specific implementation
  • We risk causing runtime errors when we pass an immutable implementation at the call site (e.g. when calling a method). To prevent this, we need to study all code that will interact with our implementation, which breaks encapsulation.

And, worst of all, there’s no fixing this without breaking backwards compatibility — Java has no choice but to stick with this sad state of affairs.

Kotlin to the rescue

The core problem is that, in Java, immutable collections are modeled as subclasses of mutable collections, when in reality, it’s the other way around. A mutable collection contains more functionality than an immutable version — it’s an immutable collection + methods that allow it to be mutated. It needs to be a subclass of its immutable variant. And this is exactly how Kotlin defines its collections.

It should be noted that so far, we’re only talking about interfacesIterableListMutableMap etc. are all interfaces, not actual implementations. The actual implementations are always mutable, but that doesn’t matter — if a method is expecting a List<T>, and List is an immutable interface, then we can safely send in a mutable implementation (e.g. ArrayList<T>) and everything will be fine, because every mutable implementation implements List. And by implements, I mean actually, safely implements, as opposed to implements but throws exceptions sometimes.

Naturally, Kotlin collections are compatible with Java. Java versions will get cast to the appropriate platform type when used in Kotlin code.

As can be seen above, the fundamental collections are ListSet and Map, and a quick overview can be found in the docs.

Constructing collections

The most basic, and most often used, way to construct collections are using the xOf() methods — listOf() for an immutable List instance, mutableSetOf() for a mutable Set instance, and so on. For the most part, their usage is fairly straightforward. The only nontrivial aspect is constructing instances of Map, where you specify key-value pairs using instances of Pair. The to infix function, which we mentioned briefly in the article on data classes, can be taken advantage of:


//sampleStart
val myList = listOf(1, 2, 3) // List { 1, 2, 3}
val mySet = setOf("A", "B", "A") // Set { "A", "B" }
val myMutableList = mutableListOf(1, 2) // MutableList { 1, 2 }
val wasModified = myMutableList.add(4) // MutableList { 1, 2, 4}
val myMap = mapOf(1 to "A", 2 to "B") // Map { 1 -> "A", 2 -> "B" }
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the maestro in the code's symphony,
        With delegates and lambdas, pure harmony.
        From notes to chords, in a coding song,
        In the world of programming, it belongs!
    """.trimIndent()
    println(poem)
}

There are also functions that specify the actual implementation to use, such as arrayListOf()hashSetOf()and others.

There are dedicated functions to create empty immutable collections. To create empty mutable variants, just use the above functions and pass no arguments. In both cases, since we aren’t passing any elements, we need to specify the type explicitly, either via a type hint or by explicitly specifying the generic parameter.


//sampleStart
val myEmptyList: List<Int> = emptyList()
val myEmptySet = emptySet<String>()
val myEmptyMutableList = mutableListOf<Double>()
//sampleEnd
fun main() {
    val poem = """
        In the code's carnival, Kotlin's the delight,
        With extension functions, it takes flight.
        From loops to spins, a coding spree,
        In the world of development, it's the key!
    """.trimIndent()
    println(poem)
}

There are various other methods of constructing collections you should be aware of:

Of those, invoking functions on other collections is by far the most used.


Frequently Asked Questions

What is the difference between List and MutableList in Kotlin?

The List interface exposes an API to work with immutable lists - that is, lists whose contents cannot be changed after they are created.

The MutableList interface extends the List interface and adds APIs to mutate the list. It is important to note that this is the exact opposite of the (wrong) way that things are done in Java, where the immutable variant inherits from the mutable one, which breaks the Liskov substitution principle.

How do I create an immutable List in Kotlin?

Since the Kotlin style guide explicitly states that immutability should be favored, almost all collection builders return immutable variants. Depending on your use-case, you could use the simple listOf() function, or maybe opt for the more flexible buildList(). Alternatively, you can also call toList() on any instance of Iterable.

How do I create a mutable List, i.e. an instance of MutableList, in Kotlin?

The most common way to create a MutableList instance in Kotlin is by using the mutableListOf function. Alternatively, you can also call toMutableList() on any instance of Iterable.

How do I convert an instance of MutableList into an instance of (immutable) List?

Use the toList() extension function, which is defined on any instance of Iterable and returns an instance of List.

How do I convert an instance of List into an instance of (immutable) MutableList?

Use the toMutableList() extension function, which is defined on any instance of Iterable and returns an instance of MutableList.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

The Kotlin Primer