2

I'm learning Kotlin while writing an Android app and I'm coming across an interesting problem with my code due to the order of execution of parent constructors and initialization.

I created a simpler example of what I'm experiencing:

class IntHolder(var i: Int)

open class A(var i: Int) {
    init {
        println("A init block id: ${getId()}") // this line calls getId() triggering the NPE
    }
    
    open fun getId(): String {
        return "A${i.toString()}"
    }
    
    override fun toString(): String {
        return "A-(i={$i})"
    }
}

class B(i: Int, var h: IntHolder): A(i) {
    init {
        println("B init block i = ${i}")
        println("  `${doSomething()}`")
    }
    
    override fun getId(): String {
        return "B-${h.i}-${super.getId()}" // NPE on this line
    }
    
    override fun toString(): String {
        return "B(i=${i})"
    }
    
    fun doSomething(): String {
        return "something ".repeat(i)
    }
}

fun main() {
    println("creating object a = A(4)")
    val a = A(4)
    println("creating object b = B(6)")
    val b = B(6, IntHolder(8)) // This is the line in main at the start of the stack trace
    println(b.doSomething())
}

or there is an editable copy here: https://pl.kotl.in/17GKHAFRa

This causes a NullPointerException when B's constructor calls A's constructor which calls getId() and since this is really an object of class B that is B.getId() and B's getId() references members of B but they haven't been initialized to the value passed into the constructor yet so I get a NullPointerException.

In reality the base class, represented by B, is mine and the parent class, represented by A, is a Java class from an Android library.

How would you recommend fixing this?

Edit:

The Base class I'm inheriting from is android.opengl.GLSurfaceView and the function being called is getHolder. It's called in init, which is called by the various constructors.

I'm following this tutorial for using a GLSurfaceView in a Live Wallpaper and it talks about overriding getHolder to call [WallpaperService.Engine].getSurfaceHolder() but was unspecific as to how so I passed WallpaperSerice.Engine into my class that inherits from GLSurfaceView so its getHolder can call it's getSurfaceHolder

5
  • If A is from the Android SDK then this could be an XY problem. Are you sure you are using A correctly? Can you actually name which class from which Android library A is? In any case, A calling getId in the constructor like that suggests that the ID is supposed to be computed from at most i. Commented Jul 17, 2021 at 3:40
  • It depends on what you mean by "fixing this". General rule is that you should not do too much in the constructor. Compiler won't disallow you to do this, but if you mess something, you will get problems Your case is just one example. Commented Jul 17, 2021 at 7:20
  • By "fixing this" I mean have a working program given which parts of code I own/control. How would you fix it or would you write this? Optimal or ideal solutions would be nice, but I'd like to get it working. Commented Jul 17, 2021 at 14:20
  • You need to read that tutorial carefully. You must have a GLWallpaperService class which contains GLEngine which then contains the WallpaperGLSurfaceView. arrange your classes in this way and the getSurfaceHolder() issue will be resolved. Commented Jul 17, 2021 at 14:50
  • Yes, I just visited their GitHub and quickly realized my view wasn't an inner class of my engine and fixed that. Such a stupid mistake… Thanks! Commented Jul 17, 2021 at 15:39

1 Answer 1

3

This is covered by Effective Java Item 19 :

Design and document for inheritance or else prohibit it

Constructors must not invoke overridable methods, directly or indirectly. If you violate this rule, program failure will result. The superclass constructor runs before the subclass constructor, so the overriding method in the subclass will get invoked before the subclass constructor has run. If the overriding method depends on any initialization performed by the subclass constructor, the method will not behave as expected. To make this concrete, here’s a class that violates this rule:

public class Super {
   // Broken - constructor invokes an overridable method
   public Super() {
       overrideMe();
   }
   public void overrideMe() { }
}

Here’s a subclass that overrides the overrideMe method, which is erroneously invoked by Super’s sole constructor:

public final class Sub extends Super {
    // Blank final, set by constructor
    private final Instant instant;
    Sub() {
       instant = Instant.now();
    }

    // Overriding method invoked by superclass constructor
    @Override public void overrideMe() {
        System.out.println(instant);
    }

    public static void main(String[] args) {
       Sub sub = new Sub();
       sub.overrideMe();
    }
}

You might expect this program to print out the instant twice, but it prints out null the first time because overrideMe is invoked by the Super constructor before the Sub constructor has a chance to initialize the instant field. Note that this program observes a final field in two different states! Note also that if overrideMe had invoked any method on instant, it would have thrown a NullPointerException when the Super constructor invoked overrideMe. The only reason this program doesn’t throw a NullPointerException as it stands is that the println method tolerates null parameters.

Sign up to request clarification or add additional context in comments.

3 Comments

It could be worth noting that this case was explicitly described in Kotlin null-safety documentation as one of very few cases when Kotlin may throw NPE: "A superclass constructor calls an open member whose implementation in the derived class uses an uninitialized state."
OK, This seems like a great re-stating of the problem, but doesn't seem to be an answer.
So you are asking how to invoke an overridable method in a cotr without causing an issue in any condition. sorry that is not possible with current java specification

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.