简体   繁体   English

在父类构造函数/初始化块运行之前未初始化值的 Kotlin 空指针异常

[英]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.我在编写 Android 应用程序时正在学习 Kotlin,但由于父构造函数和初始化的执行顺序,我的代码遇到了一个有趣的问题。

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或者这里有一个可编辑的副本: 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 .这会导致 NullPointerException 当B的构造函数调用A的构造函数调用getId()并且因为这实际上是B类的对象,即B.getId()BgetId()引用B成员但他们没有'尚未初始化为传递给构造函数的值,所以我得到一个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.实际上,由B表示的基类是我的,而由A表示的父类是来自 Android 库的 Java 类。

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 .我继承的基类是android.opengl.GLSurfaceView ,被调用的函数是getHolder It's called in init, which is called by the various constructors . 它在 init 中调用,由各种构造函数调用

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 我正在关注这个在动态壁纸中使用 GLSurfaceView 的教程,它讨论了覆盖getHolder来调用[WallpaperService.Engine].getSurfaceHolder()但没有具体说明我如何将WallpaperSerice.Engine传递到我从GLSurfaceView继承的类中所以它的getHolder可以称之为getSurfaceHolder

This is covered by Effective Java Item 19 :这包含在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:这是一个覆盖 overrideMe 方法的子类,该方法被 Super 的唯一构造函数错误地调用:

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.您可能希望此程序打印出即时两次,但第一次打印出 null,因为 Super 构造函数在 Sub 构造函数有机会初始化即时字段之前调用了 overrideMe。 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.另请注意,如果 overrideMe 立即调用了任何方法,则在 Super 构造函数调用 overrideMe 时它会抛出 NullPointerException。 The only reason this program doesn't throw a NullPointerException as it stands is that the println method tolerates null parameters.这个程序没有抛出 NullPointerException 的唯一原因是 println 方法容忍空参数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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