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, hashCode
, equals
and 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 copy()
function.
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!
Top comments (2)
Thanks, very usefull. I was struggling with immutability and basic operations like changin property values.
Thanks!