You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何无法从父类构造器访问子类构造参数?及解决方案

问题分析与解决方案

为什么会出现NPE?

当创建子类B的实例时,基于JVM的初始化顺序规则如下:

  1. 优先执行父类A的构造逻辑,包括init块中的代码;
  2. 父类init块调用了抽象方法f(),而该方法被子类B重写;
  3. 此时子类B的成员属性p还未完成初始化——子类的所有成员(包括主构造中声明的val/var参数),都要等父类构造完全结束后才会赋值;
  4. 因此在B的f()方法中访问p时,它还是null,调用p()自然抛出NullPointerException。

解决方案

方案一:将父类初始化逻辑改为手动调用的方法

把父类init块中的逻辑抽成独立方法,在子类实例创建完成后再调用,确保子类成员已经初始化:

abstract class A {
    // 原init逻辑改为公开的初始化方法
    fun initialize() {
        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")
}.apply { initialize() }

这种方式直接规避了父类构造时访问子类未初始化成员的问题,逻辑清晰可控。

方案二:延迟子类成员初始化并做安全检查

使用lateinit标记成员,在调用前检查是否已初始化,避免NPE:

abstract class A {
    init {
        f()
    }

    abstract fun f()
}

class B : A() {
    lateinit var p: () -> Unit

    // 用次构造器接收参数,在父类构造完成后赋值
    constructor(p: () -> Unit) : this() {
        this.p = p
    }

    override fun f() {
        // 检查p是否已初始化,再执行调用
        if (::p.isInitialized) {
            try {
                p()
            } catch (c: Throwable) {
                println(c)
            }
        }
    }
}

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

注意:该方案下,父类init调用f()时p还未初始化,因此不会执行p(),如果需要父类构造阶段就执行目标逻辑,此方案不适用。

方案三:调整父类设计,直接接收初始化逻辑

如果父类init块的作用就是执行某个初始化动作,可以把该动作作为参数传给父类,避免子类重写方法:

abstract class A(private val initAction: () -> Unit) {
    init {
        initAction()
    }
}

class B(val p: () -> Unit) : A(p) {
    // 若不需要额外逻辑,无需重写方法
}

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

这种方式从根源上避免了父类构造时访问子类未初始化成员的问题,代码更简洁。

内容的提问来源于stack exchange,提问作者Alexey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 06:31:10