The following code is valid Kotlin code:
abstract class A {
protected lateinit var v: X
abstract fun f(): X
class SubA : A() {
override fun f(): X {
return SubX()
}
init {
v = f()
}
}
}
It defines an abstract class which has a lateinit var
field and an abstract method that sets the value of that field. The reason behind this is that that method may be called later again, and its behavior should be defined in the subclasses that extend the original class.
This code is a simplification of a real-world code, and even though it works, I feel like it is messy since the developer of the subclass could choose not to (or forget) to call v = f()
inside an init
block. And we cannot do that in A
either because then it will show a warning that we are calling a non-final method in the constructor. What I propose is the following:
abstract class A {
private lateinit var v: X
abstract fun f(): X
class SubA : A() {
override fun f(): X {
return SubX()
}
}
lateinit { // this does not exist
v = f()
}
}
The benefits of this is that now the field can be private
instead of protected
, and the developer does not have to manually call v = f()
in each of their subclasses (or the subclasses of their subclasses), and the naming fits with the nomenclature of Kotlin since lateinit
is already a keyword and init
is already a block. The only difference between an init
and a lateinit
block would be that the contents of a lateinit
block are executed after the subclass constructors, not before like init
.
My question is, why isn't this a thing? Is this already possible with some other syntax that I do not know about? If not, do you think it's something that should be added to Kotlin? How and where can I make this suggestion so that the developers would most likely see it?
CodePudding user response:
There are three options, and you can implement your lateinit
block in two ways
- don't lazy init - just have a normal construction parameter
- use a delegated lazy property
- add a lambda construction parameter to the superclass
class A
All of these solves the problem of requiring subclasses of A
having to perform some initialization task. The behaviour is encapsulated within class A
.
Normal construction parameter
Normally I'd prefer this approach, and don't lazy init. It's usually not needed.
abstract class A(val v: X)
class SubA : A(SubX())
interface X
class SubX : X
fun f()
can be replaced entirely by val v
.
This has many advantages, primarily that it's easier to understand, manage because it's immutable, and update as your application changes.
Delegated lazy property
Assuming lazy initialization is required, and based on the example you've provided, I prefer the delegated lazy property approach.
The existing equivalent of your proposed lateinit
block is a lazy property.
abstract class A {
protected val v: X by lazy { f() }
abstract fun f(): X
}
class SubA : A() {
override fun f(): X {
return SubX()
}
}
interface X
class SubX : X
The superclass can simply call the function f()
from within the lazy {}
block.
The lazy block will only run once, if it is required.
Construction parameter
Alternatively the superclass can define a lambda as construction parameter, which returns an X
.
Using a lambda as a construction parameter might be preferred if the providers are independent of implementations of class A
, so they can be defined separately, which helps with testing and re-used.
fun interface ValueProvider : () -> X
abstract class A(
private val valueProvider: ValueProvider
) {
protected val v: X get() = valueProvider()
}
class SubA : A(ValueProvider { SubX() })
interface X
class SubX : X
The construction parameter replaces the need for fun f()
.
To make things crystal clear I've also defined the lambda as ValueProvider
. This also makes it easier to find usages, and to define some KDoc on it.
For some variety, I haven't used a lazy delegate here. Because val v
has a getter defined (get() = ...
), valueProvider
will always be invoked. But, if needed, a lazy property can be used again.
abstract class A(
private val valueProvider: ValueProvider
) {
protected val v: X by lazy(valueProvider)
}