Mutable vals in Kotlin
When I first learned Kotlin, the difference between val
and var
seemed simple: val
means immutable and var
means mutable.
The truth is more nuanced than that: val
does not mean immutable, val
means read-only. That means that you're not allowed to explicitly write to a val
, but it doesn't guarantee that they're immutable[1].
Mutable Class Properties
For variables, the distinction between immutable and read-only is a moot point. There's no way to write a val
variable or override how it is retrieved, so it is (for all intents and purposes) immutable.
For class properties, however, the read-only nature of val
makes a huge difference.
In the context of properties, val
vs. var
indicates whether getters/setters exist for the property. A var
has both a getter and a setter, whereas a val
only has a getter.
In the simple case, the lack of a setter means that val
class properties are immutable. However, it is possible to add a custom getter function for any class property, allowing you to return whatever you want each time someone accesses the property. For example:
class Person(val birthDay: DateTime) {
val age: Int
get() = yearsBetween(birthDay, DateTime.now())
}
As you can see, there's no explicit way to set Person.age
, but Person.age
will change values as the current date changes.
In fact, thinking of Person.age
as a variable at all is a misnomer. It's actually a getter function that you're calling that may change values over invocations.
Consequences
I was personally horrified when I learned about mutable vals in Kotlin. I felt betrayed - val
vs. var
marking data as immutable vs. mutable was one of the first cool features from Kotlin I learned!
As far as I can tell, there are two arguments to be made for customizable getters for val
class properties:
- Class properties are just a shorthand for getters/setters, and customizing getters is generally thought of as okay.
- Customizable getters enable delegated properties.
Delegated properties are a compelling use case and I will continue to use them. However, I find it much easier to reason about code when val
implies an immutable reference. Immutability makes code (especially concurrent code) much easier to work with.
As such, I have chosen not to use custom getters for val
class properties. If a read-only class property changes value over time, I instead replace that property with a normal function:
class Person(val birthDay: DateTime) {
fun age(): Int = yearsBetween(birthDay, DateTime.now())
}
This preference is what the Kotlin coding conventions recommends anyways. It states that you should prefer a property over a function only when the underlying algorithm:
- does not throw
- has a O(1) complexity
- is cheap to calculate (or caсhed on the first run)
- returns the same result over invocations
Overriding the getter and changing the reference violates the last condition, so avoid doing it!
For brevity's sake, in this post "immutable" actually means "immutable reference." You can always reference a mutable object (like an ArrayList). Protecting against mutability in that case requires more advanced techniques than simply using val, which I'm not going into here. ↩︎