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 add
, remove
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 implementingList
, 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 interfaces. Iterable
, List
, MutableMap
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 List
, Set
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:
- using builder functions (which we touched upon when talking about DSLs)
- concrete type constructors (and, for
List
, initializer functions) - copying
- invoking functions on other collections
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?
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?
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?
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
?
MutableList
into an instance of (immutable) List
?How do I convert an instance of List
into an instance of (immutable) MutableList
?
List
into an instance of (immutable) MutableList
?Use the
extension function, which is defined on any instance of toMutableList()
Iterable
and returns an instance of
.MutableList