8 min read

Unlike Clojure, Haskell, F#, and the likes, Kotlin is not a pure functional programming language, where immutability is forced; rather, we may refer to Kotlin as a perfect blend of functional programming and OOP languages. It contains the major benefits of both worlds.

So, instead of forcing immutability like pure functional programming languages, Kotlin encourages immutability, giving it automatic preference wherever possible. In this article, we’ll understand the various methods of implementing immutability in Kotlin.

This article has been taken from the book, Functional Kotlin, by Mario Arias and Rivu Chakraborty.

In other words, Kotlin has immutable variables (val), but no language mechanisms that would guarantee true deep immutability of the state. If a val variable references a mutable object, its contents can still be modified. We will have a more elaborate discussion and a deeper dive on this topic, but first let us have a look at how we can get referential immutability in Kotlin and the differences between varval, and const val.

By true deep immutability of the state, we mean a property will always return the same value whenever it is called and that the property never changes its value; we can easily avoid this if we have a val  property that has a custom getter. You can find more details at the following link: https://artemzin.com/blog/kotlin-val-does-not-mean-immutable-it-just-means-readonly-yeah/

The difference between var and val

So, in order to encourage immutability but still let the developers have the choice, Kotlin introduced two types of variables. The first one is var, which is just a simple variable, just like in any imperative language. On the other hand, val brings us a bit closer to immutability; again, it doesn’t guarantee immutability. So, what exactly does the val variable provide us? It enforces read-only, you cannot write into a val variable after initialization. So, if you use a val variable without a custom getter, you can achieve referential immutability.

Let’s have a look; the following program will not compile:

fun main(args: Array) { 
    val x:String = "Kotlin" 
    x+="Immutable"//(1) 
}

As I mentioned earlier, the preceding program will not compile; it will give an error on comment (1). As we’ve declared variable x as valx will be read-only and once we initialize x; we cannot modify it afterward.

So, now you’re probably asking why we cannot guarantee immutability with val ? Let’s inspect this with the following example:

object MutableVal { 
    var count = 0 
    val myString:String = "Mutable" 
        get() {//(1) 
            return "$field ${++count}"//(2) 
        } 
} 
 
fun main(args: Array) { 
    println("Calling 1st time ${MutableVal.myString}") 
    println("Calling 2nd time ${MutableVal.myString}") 
    println("Calling 3rd time ${MutableVal.myString}")//(3) 
}

In this program, we declared myString as a val property, but implemented a custom get function, where we tweaked the value of myString before returning it. Have a look at the output first, then we will further look into the program:

As you can see, the myString property, despite being val, returned different values every time we accessed it. So, now, let us look into the code to understand such behavior.

On comment (1), we declared a custom getter for the val property myString. On comment (2), we pre-incremented the value of count and added it after the value of the field value, myString, and returned the same from the getter. So, whenever we requested the myString property, count got incremented and, on the next request, we got a different value. As a result, we broke the immutable behavior of a val property.

Compile time constants

So, how can we overcome this? How can we enforce immutability? The const val properties are here to help us. Just modify val myString with const val myString and you cannot implement the custom getter.

While val properties are read-only variables, const val, on the other hand, are compile time constants. You cannot assign the outcome (result) of a function to const val. Let’s discuss some of the differences between val and const val:

  • The val properties are read-only variables, while const val are compile time constants
  • The val properties can have custom getters, but const val cannot
  • We can have val properties anywhere in our Kotlin code, inside functions, as a class member, anywhere, but const val has to be a top-level member of a class/object
  • You cannot write delegates for the const val properties
  • We can have the val property of any type, be it our custom class or any primitive data type, but only primitive data types and String are allowed with a const val property
  • We cannot have nullable data types with the const val properties; as a result, we cannot have null values for the const val properties either

As a result, the const val properties guarantee immutability of value but have lesser flexibility and you are bound to use only primitive data types with const val, which cannot always serve our purposes.

Now, that I’ve used the word referential immutability quite a few times, let us now inspect what it means and how many types of immutability there are.

Types of immutability

There are basically the following two types of immutability:

  • Referential immutability
  • Immutable values

Immutable reference  (referential immutability)

Referential immutability enforces that, once a reference is assigned, it can’t be assigned to something else. Think of having it as a val property of a custom class, or even MutableList or MutableMap; after you initialize the property, you cannot reference something else from that property, except the underlying value from the object. For example, take the following program:

class MutableObj { 
    var value = "" 
 
    override fun toString(): String { 
        return "MutableObj(value='$value')" 
    } 
} 
 
fun main(args: Array) { 
    val mutableObj:MutableObj = MutableObj()//(1) 
    println("MutableObj $mutableObj") 
    mutableObj.value = "Changed"//(2) 
    println("MutableObj $mutableObj") 
 
    val list = mutableListOf("a","b","c","d","e")//(3) 
    println(list) 
    list.add("f")//(4) 
    println(list) 
}

Have a look at the output before we proceed with explaining the program:

So, in this program we’ve two val properties—list and mutableObj. We initialized mutableObj with the default constructor of MutableObj, since it’s a val property it’ll always refer to that specific object; but, if you concentrate on comment (2), we changed the value property of mutableObj, as the value property of the MutableObj class is mutable (var).

It’s the same with the list property, we can add items to the list after initialization, changing its underlying value. Both list and mutableObj are perfect examples of immutable reference; once initialized, the properties can’t be assigned to something else, but their underlying values can be changed (you can refer the output). The reason behind that is the data type we used to assign to those properties. Both the MutableObj class and the MutableList data structures are mutable themselves, so we cannot restrict value changes for their instances.

Immutable values

The immutable values, on the other hand, enforce no change on values as well; it is really complex to maintain. In Kotlin, the const val properties enforce immutability of value, but they lack flexibility (we already discussed them) and you’re bound to use only primitive types, which can be troublesome in real-life scenarios.

Immutable collections

Kotlin gives preference to immutability wherever possible, but leaves the choice to the developer whether or when to use it. This power of choice makes the language even more powerful. Unlike most languages, where they have either only mutable (like Java, C#, and so on) or only immutable collections (like F#, Haskell, Clojure, and so on), Kotlin has both and distinguishes between them, leaving the developer with the freedom to choose whether to use an immutable or mutable one.

Kotlin has two interfaces for collection objects—Collection and MutableCollection; all the collection classes (for example, ListSet, or Map) implement either of them. As the name suggests, the two interfaces are designed to serve immutable and mutable collections respectively. Let us have an example:

fun main(args: Array) { 
    val immutableList = listOf(1,2,3,4,5,6,7)//(1) 
    println("Immutable List $immutableList") 
    val mutableList:MutableList = immutableList.toMutableList()//(2) 
    println("Mutable List $mutableList") 
    mutableList.add(8)//(3) 
    println("Mutable List after add $mutableList") 
    println("Mutable List after add $immutableList") 
}

The output is as follows:

So, in this program, we created an immutable list with the help of the listOf method of Kotlin, on comment (1). The listOf method creates an immutable list with the elements (varargs) passed to it. This method also has a generic type parameter, which can be skipped if the elements array is not empty. The listOf method also has a mutable version—mutableListOf() which is identical except that it returns MutableList instead.

We can convert an immutable list to a mutable one with the help of the toMutableList() extension function, we did the same in comment (2), to add an element to it on comment (3). However, if you check the output, the original Immutable List remains the same without any changes, the item is, however, added to the newly created MutableList instead.

So now you know how to implement immutability in Kotlin. If you found this tutorial helpful, and would like to learn more, head on over to purchase the full book, Functional Kotlin, by Mario Arias and Rivu Chakraborty.

Read Next:

Extension functions in Kotlin: everything you need to know

Building RESTful web services with Kotlin

Building chat application with Kotlin using Node.js, the powerful Server-side JavaScript platform


Subscribe to the weekly Packt Hub newsletter. We'll send you the results of our AI Now Survey, featuring data and insights from across the tech landscape.

* indicates required