Note that the problem is really because p is a property of the subclass. It can’t be initialized by the time the constructor of A runs for fundamental reasons of how object-oriented programming has to lay out memory ; subclasses sharing the memory layout of their parents and adding stuff to it, it has to be initialized afterwards (or more exactly, it would probably be technically possible to do it the other way around but you’d have the exact same problem in the reverse order which would be a concrete problem a lot more often, and that’s why Kotlin like all other OO languages do it in this order).
So once you’ve noticed the problem is really contingent on the method being stored in a property, it becomes somewhat easy to address it concretely. You can do this simply by having f() not being passed by the parent, but be a method of the class. You’d have all your subclasses of A writing their own validation code in their body.
class B : A {
override fun f() {
println("p() called")
}
}
This will work. It is however pretty error-prone because it’s really easy to add a property (even an immutable one !) in B and think it’s safe to use it in f()
while it will be null when called, so ponder this carefully.
But this does show what might be better OO design than your original code, as a class should generally validate one kind of data and another kind of data requiring different validation would generally be a different class in this kind of design. Though of course that’s just a generality and I can’t assess for sure what’s really better in your case without knowing your concrete use case.
However I want to point out that you can write this in a way that is very close to what you have by leveraging anonymous classes, AND a little bit less error-prone.
val b = object : A() {
override fun f() {
println("p() called")
}
}
This declares an anonymous class and is functionally equivalent to declaring and naming B, but it’s close syntactically to what you had and it will work. You still can’t reference properties of the anonymous subclass in the function, for the same reason. Or in code terms :
val b = object : A() {
val prop = "prop"
override fun f() {
print(prop) // <-- prints null, because when the constructor for A is called this is not yet initialized
print(prop.length) // <-- crashes with NPE, because Kotlin's null checker is fooled (a pretty rare instance) by this being called in the superclass constructor
}
}
Maybe it’s a little bit less tempting to add a property in the anonymous class than in a named class because you’d generally prefer to keep your inline code short, but I’d still assess the risk as pretty salient.
Interestingly I found that you can capture local variables in the same way that you can in your original code. Or in code terms :
val s = "message"
val b = object : A() {
val prop = "prop"
override fun f() {
print(s) // <-- prints "message", curiously
}
}
You might be wondering why the capture works. At the end of the day, the capture is just syntactic sugar for a field that the compiler adds to your anonymous class, so why doesn’t it behave the same as the property ? It turns out Kotlin initializes the captured fields before it calls the constructor of the super class. The constructor of the anonymous class compiles to (over the JVM) :
<init>(Ljava/lang/String;)V
// annotable parameter count: 1 (visible)
// annotable parameter count: 1 (invisible)
L0
LINENUMBER 66 L0
ALOAD 0
ALOAD 1
PUTFIELD <class>$<method>$b$1.$s : Ljava/lang/String;
ALOAD 0
L1
LINENUMBER 66 L1
INVOKESPECIAL A.<init> ()V
RETURN
L2
LOCALVARIABLE this <class>$<method>$b$1; L0 L2 0
LOCALVARIABLE $captured_local_variable$0 Ljava/lang/String; L0 L2 1
MAXSTACK = 2
MAXLOCALS = 2
Note how PUTFIELD
for the captured variable precedes the call to A.<init>
. I do not know whether this behavior is considered stable in Kotlin ; in doubt I would not rely on it, unless you can find this documented somewhere about the compiler.
Okay so all of this is pretty error-prone. If possible at all, I’d recommend avoiding calling abstract methods in the constructor if you can find another design that you like. One thing I could suggest is to have A be aware that its subclasses will need to validate their input, and take the validator as an argument to its constructor. It could be just lambas as you have now if it’s very simple, or a little bit more fleshed out validator classes if you need something more structured.