简体   繁体   中英

Kotlin Null Pointer Exception from value not being initialized prior to parent classes constructor/init block running

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM