Kotlin has direct support for immutable data structures. They are called data classes and they have some really nice features.
First of all, the data class creation is really simple. For instance, this
User data class will create class with one full-args constructor,
toString methods generated:
data class User( val firstName: String, val lastName: String, val age: Int )
There's additional advantage here, that by using
val the references to primitive properties are final - thus, cannot be changed. You also need to define all constructor parameters when creating new instance, e.g.
User("John", "Doe", 50). That's a good start for building immutable data structures on your own!
The most basic operation you'll want to do with immutable data structure is creating new instance out of existing one and changing (some of) its parameters. This is nicely supported in Kotlin by
Let's start by simply copying the instance:
val user1 = User("John", "Doe", 50) val user2 = user1.copy() user1 === user2 // referential equality = false user1 == user2 // structural equality = true
You can see that it works as expected - objects are equal, but instances are not the same. What if we want to change one property?
val user1 = User("John", "Doe", 50) val user2 = user1.copy(age = 20)
This will copy the instance and change
age parameter to 20. Compared to Java (in which cloning/copying without 3rd party library is nearly impossible) this is really awesome!
Let's look at a bit more complex example where we don't use only primitive types but reference to a object instance:
data class System( val name: String, val user: User ) val user = User("Jane", "Doe", 50) val system1 = System("Blog", user) val system2 = system1.copy() system1 === system2 // false system1.user === system2.user // true
So as you can see
copy() is not deep, which means that parameters that are referring to instances will only copy their references. So basically, it's a syntactic sugar over creating new instance via it's constructor and passing the parameters of original instnace.
But that's okay, since user is an immutable data class and it's properties cannot be changed. It would only be problematic if
User would have properties defined as
var. Then you would be able to do something like this:
... val system2 = system1.copy() system1.user.age = 13 // not possible if user is defined as `val` println(system2.user.age) // prints "13"
The bottom line is, if you keep using
val definitions consistently you should be safe with immutability!