Kotlin中构造函数调用抽象方法初始化变量失效问题咨询
这个问题的核心是Kotlin与Java的类初始化顺序差异。在Kotlin中,创建子类实例的执行顺序是:
- 先执行父类的所有
init块和构造逻辑 - 再初始化子类的成员变量(也就是你写的
=404这类赋值操作) - 最后执行子类的
init块
所以当父类init块调用getUniforms()给子类的loc_projectionMatrix等变量赋值为0、1、2时,这些值会被后续子类成员变量的=404覆盖,导致最终dump()输出还是默认值。而Java中如果你的代码没有显式给成员变量赋值默认值(或赋值逻辑在构造函数内),就不会触发这个覆盖问题。
下面提供两种无需手动调用初始化函数的可行解决方案:
方案1:使用Delegates.notNull()委托(推荐)
Kotlin的kotlin.properties.Delegates.notNull()允许你声明非空的基本类型变量,且不需要设置默认值(从根源避免覆盖)。它要求变量必须在第一次使用前完成赋值,刚好匹配你的场景——父类init块调用getUniforms()完成赋值。
修改BoilerplateShader的变量声明:
import kotlin.properties.Delegates abstract class BoilerplateShader() : Shader(){ var loc_projectionMatrix: Int by Delegates.notNull() var loc_transformationMatrix: Int by Delegates.notNull() var loc_viewMatrix: Int by Delegates.notNull() override fun getUniforms(){ loc_projectionMatrix = 0 loc_transformationMatrix = 1 loc_viewMatrix = 2 println(loc_projectionMatrix.toString() + ", " + loc_transformationMatrix + ", " + loc_viewMatrix) } // 其他代码保持不变 }
这样子类变量不会有默认值赋值步骤,父类init块中getUniforms()的赋值会被保留,执行dump()时就能得到正确的0、1、2。
方案2:调整初始化顺序,将父类逻辑移到子类init块执行
把父类init块的逻辑提取为一个受保护的方法,然后在子类的init块中调用它。由于子类的init块是在子类成员变量初始化之后执行的,此时赋值会覆盖默认的404:
首先修改父类Shader:
abstract class Shader { // 将原init块逻辑迁移至此 protected open fun initializeShader() { bindAttribs() getUniforms() } abstract fun getUniforms() abstract fun bindAttribs() }
然后修改子类BoilerplateShader:
abstract class BoilerplateShader() : Shader(){ var loc_projectionMatrix: Int = 404 var loc_transformationMatrix: Int = 404 var loc_viewMatrix: Int = 404 init { initializeShader() // 子类init块调用,此时成员变量已完成默认初始化,赋值会覆盖404 } override fun getUniforms(){ loc_projectionMatrix = 0 loc_transformationMatrix = 1 loc_viewMatrix = 2 println(loc_projectionMatrix.toString() + ", " + loc_transformationMatrix + ", " + loc_viewMatrix) } // 其他代码保持不变 }
这种方案不需要修改变量类型,仅调整了初始化逻辑的执行时机,同样能解决问题。
额外提醒
虽然Java中类似代码可能表现正常,但在父类构造中调用子类抽象方法本质上是一种不安全的实践(无论Java还是Kotlin),因为此时子类可能尚未完全初始化。如果条件允许,建议重构设计避免这种写法;若必须保留现有结构,上述两种方案均可满足你的需求。
内容的提问来源于stack exchange,提问作者Zoe - Save the data dump

