如何确保基类OptionsAndAnswer的validate方法被所有子类自动调用?
解决方案
当然可以让所有子类自动继承验证逻辑,无需每个子类显式调用validate(),但要避开基类init执行时子类属性未初始化的问题,分两种场景处理:
场景1:子类的options通过构造参数直接传递给基类
如果子类和示例一致,options是构造参数且直接传给基类构造函数,可直接在基类的init块中调用validate(),不会触发NPE。此时基类的options已被赋值为子类传入的列表,初始化顺序不存在问题:
修改后的基类代码
abstract class OptionsAndAnswer( ..., open val options: List<Option> ){ init { validate() } fun validate() { require(options.map { it.id }.distinct().size == options.size) { "all option ids should be unique" } } }
子类代码(无需再写init { validate() })
data class StringOptionsAndAnswer( ..., override val options: List<StringOption> ): OptionsAndAnswer(answerId, options)
场景2:子类的options在自身init块中初始化
如果子类的options是延迟初始化(比如用lateinit)或在子类init中赋值,基类init执行时options尚未就绪,会触发NPE。这种情况下更可靠的方式是将基类的options改为非open,子类通过构造参数传入符合要求的列表,利用Kotlin的协变特性兼容子类特定类型:
基类代码(去掉open修饰符)
abstract class OptionsAndAnswer( ..., val options: List<Option> ){ init { validate() } fun validate() { require(options.map { it.id }.distinct().size == options.size) { "all option ids should be unique" } } }
子类代码(无需override,直接传入子类类型列表)
data class StringOptionsAndAnswer( ..., val options: List<StringOption> ): OptionsAndAnswer(answerId, options)
由于List在Kotlin中是协变的,List<StringOption>可安全赋值给List<Option>(只要StringOption继承自Option),既保证基类验证逻辑自动执行,又不影响子类使用特定类型的选项列表。
为什么直接放基类init会触发NPE?
问题源于Kotlin的初始化顺序:基类的init块会优先于子类的init块执行。如果子类的options是在自身init中赋值的,基类init调用validate()时,options还未完成初始化,就会抛出空指针异常。但如果options通过构造参数传入基类,基类构造时已拿到有效列表,就不会出现这个问题。
内容的提问来源于stack exchange,提问作者Kun.tito
相关产品推荐
相关产品推荐

