I just started to explore the language Kotlin. I'm struggling with inheritance, var&val and side-effects.
If I declare a trait A
with a val x
and override x
in AImpl
it is possible to override it as var
(see code below). Surprisingly the print()
method in A
is affected by the reassignment of x
even though x
is a value in A
. Is this a bug or a feature?
Code:
trait A {
fun print() {
println("A.x = $x")
}
val x : Int;
}
class AImpl(x : Int) : A {
override var x = x; // seems like x can be overriden as `var`
}
fun main(args: Array<String>) {
val a = AImpl(2)
a.print() // A.x = 2
a.x = 3; // x can be changed
// even though print() is defined in trait A
// where x is val it prints x = 3
a.print() // A.x = 3
}
I'm aware of the fact that if I define a
with type A
explicitly it is not allowed to change x
:
val a = AImpl(2) : A
a.x = 3 // ERROR: value x cannot be reassigned
But as the first case shows, inheritance can cause side effects which are clearly not intended in A
. How do I protect values from being changed by inheritance?
I would consider this a feature, as changing val to var imposes weaker usage restrictions and can't break any superclass code. Similar situation can be observed with visibility modifiers:
In this example visibility restrictions are also relaxed by a subclass, although some people are considering this technique as an antipattern.
In kotlin you can explicitly define if any particular class member can be overriden by a subclass using
open
modifier. In traits, however, all the members are open by default. The solution is to replace trait with class, so you'll be able to control inheritance:You can make your
val
final
, i.e. forbid overriding it at all. If you define aval
in a class, it isfinal
by default.Also, if you need to override a
val
with avar
, but do not want the setter to be public, you can say so:Overriding a
val
with avar
is a feature. It is equivalent to adding a set-method while in the superclass there was only a get-method. And this is rather important in implementing some patterns, such as read-only interfaces.There's no way to "protect" your
val
from being overridden in a way that allows changing mutation other than making itfinal
, becauseval
does not mean "immutable reference", but merely "read-only property". In other words, when your traitA
declares aval
, it means that through a reference of typeA
the client can not write thisval
, no other guarantees intended, or indeed possible.P.S. Semicolons are optional in Kotlin, feel free to omit them altogether