Access child class constructor parameter from parent's constructor

Look at the following code:

abstract class A {

    init {
        f()
    }

    abstract fun f()
}

class B(val p: () -> Unit) : A() {

    override fun f() {
        try {
            p()
        } catch (c: Throwable) {
            println(c)
        }
    }
}

val b = B {
    println("p() called")
}

Expecting p() called will be printed, actual result is NPE:

java.lang.NullPointerException: Cannot invoke "kotlin.jvm.functions.Function0.invoke()" because "this.p" is null

Could you please explain, why can’t I access child constructor parameter from parent’s constructor? And how can I deal with this?

This is because the super class is initialized first, and then the subclass. Calling the logic of the subclass while initializing the super class is asking for trouble, and I believe the compiler even generates a warning when we do this. It is not entirely bad, but we need to be careful to not get into problems as in your example.

To fix the problem you probably need to redesign your code. Ideally, super class should be self-contained and could initialize itself. Why do you need the subclass in order to properly initialize the super class?

2 Likes

Unfortunately I had a use-case for this. :frowning: I had an abstract parent class, and various subclasses. I wanted to validate the data passed to each class when it was created. So I had the idea to create an abstract validate method in the parent class, and call it in the parent init block, and have each subclass override that validate method, as each subclass had its own logic for what counted as valid data. Due to the “super class is initialized first” thing, this (perfectly valid imo) use-case was not possible. :frowning:

1 Like

This is only my opinion, but I think constructors are for initializing properties and applying a simple logic, mostly internal to the class, not spanning across multiple classes. People like to use constructors for “autorunning” behavior, they put complicated logic, even perform some I/O or CPU heavy operations there, they shoot themselves in the foot and then they ask how to make it hurt less.

Question is: how to do it better? Running the subtype constructor before the super constructor makes even less sense. Not to mention running them somehow together. Super before sub is a simple rule, it makes the most sense, and it just works if we only don’t put too much logic to constructors.

Composition over inheritance is another suggestion from my side. We don’t have problems with subclasses, if we don’t have subclasses :wink:

2 Likes

I understand not doing CPU-heavy or I/O-heavy stuff in a constructor, but I do think you can do a bit of stuff; otherwise, why have init blocks at all?

Well I mean, to do it “better”, I would expect all the compiled code to be available before I start constructing an object. Maybe this is just my lack of understanding of how object initialization works, but it seems very weird to me that part of initializing a class includes “defining the functions”. I thought that was what the compiler did. Constructors and init blocks are for setting values and stuff, I thought. Does implementing an abstract function count as “setting a value”??

And your understanding is correct. We can call an abstract function in the super constructor and it should work as expected. The problem is that the function in the subclass is then executed at the time the subclass is not yet initialized. So if you access any child members from the function, you will get problems.

Note that the compiler doesn’t generate a compile error, but a warning. It compiles this code and the code executes as expected. If you know you won’t use any members in any implementation of the abstract function, you can simply suppress the warning. Still, I think in this case it would make more sense to pass a function to the super constructor.

Hmm… I didn’t think I was using any properties from the child class in the validate function, but maybe I was. It’s been a while since I looked at that code, so I don’t remember.

In your example val p is a property of the subclass, so if in your real code you’re doing something similar then that’s where the problem is.

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.

3 Likes