In Kotlin if you have an open
class which refers to this
in its constructor or init
block, you (quite rightly) get a compiler warning:
Leaking 'this' in constructor of non-final class
The reason for this is explained here.
My question is: why is this not reported when the class is final? If this
is used in the init
block before that block has completed, the object is still not in a fully constructed state, so shouldn't the warning apply there too?
This can even lead to a situation where a val
property seems to change at runtime. Take this code as an example:
class Listener {
fun onCreated(leaker: Leaker) = println("Listener hears that leaker created with a value of ${leaker.myVal}")
}
class Leaker(listener: Listener) {
val myVal: Int
init {
listener.onCreated(this)
myVal = 1
println("Leaker knows that it's been created with a value of $myVal")
}
}
Using these objects as follows:
Leaker(Listener())
will result in the following output:
Listener hears that leaker created with a value of 0
Leaker knows that it's been created with a value of 1
Notice that myVal
is initially reported as being 0, then as being 1.
As can be seen, Leaker
passes an instance of itself to Listener
before Leaker
has been fully constructed. Listener
can then access the myVal
property before it's been initialized, so it'll have the default value (0 in this case as it's an integer). Later on Listener
then changes the value of this property (to 1 in this example). This means that the program behaves as if a val
has changed.
Should the compiler warn you about this?
tl;dr: https://youtrack.jetbrains.com/issue/KT-22044 is a good fit regarding this issue.
I will cite what Intellij IDEAs inspection called "Leaking 'this' in constructor" says about this:
This inspection reports dangerous operations inside constructors including:
- Accessing a non-final property in constructor
- Calling a non-final function in constructor
- Using this as a function argument in a constructor of a non-final class
These operations are dangerous because your class can be inherited, and a derived class is not yet initialized at this moment. Typical example:
abstract class Base { val code = calculate() abstract fun calculate(): Int } class Derived(private val x: Int) : Base() { override fun calculate() = x } fun testIt() { println(Derived(42).code) // Expected: 42, actual: 0 }
I think that there should be warning nonetheless as you were able to access a non-initialized variable. The reason: the compiler already disallows direct access to uninitialized variables, i.e. the following will not compile:
class Demo {
val some : Int
init {
println(some) // Variable 'some' must be initialized
but accessing it indirectly compiles and shows the default value of the variable type:
class Demo2 {
val some : Int
val someString : String
init {
fun Demo2.indirectSome() = some
fun Demo2.indirectSomeString() = someString
println(indirectSome()) // prints 0
println(indirectSomeString()) // prints null; and yes.. this can lead to NullPointerExceptions
and there we also have a "leak", basically accessing some
before it should ;-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With