Kotlin子类中的惰性初始化

时间:2018-06-20 07:05:15

标签: kotlin lazy-initialization

我正在尝试使用在子类中初始化的属性来构建字符串。

我读到了关于延迟初始化的信息,但是按某种方式,它不起作用。

abstract class SubProcessFullNameBuilder(technicalDomain: TechnicalDomainEnumeration) {

    protected val moduleName = "td.${technicalDomain.value().toLowerCase()}.shared"

    private val packageName by lazy { packageName() }
    private val processName by lazy { processName() }

    val processFullName: String = "$moduleName/$packageName.$processName"

    protected abstract fun packageName(): String
    protected abstract fun processName(): String
}

class WorkerFullNameBuilder(
        private val jmsDirection: JmsDirectionEnumeration,
        technicalDomain: TechnicalDomainEnumeration,
        private val cdmCode: String) : SubProcessFullNameBuilder(technicalDomain) {

    override fun packageName() = "$moduleName.workers.${jmsDirection.value().toLowerCase()}.${cdmCode.toLowerCase()}"
    override fun processName() = "Worker"
}

由于我重写了packageName()processName()属性,所以我希望在调用packageName属性时,它将使用子类中的实现。

但是当我调用processFullName属性时,它会抛出一个java.lang.NullPointerException

val builder = WorkerFullNameBuilder(JmsDirectionEnumeration.ESB_IN, TechnicalDomainEnumeration.INFOR, "ccmd")
val name = builder.processFullName

如何以适当的方式初始化packageName和processName属性?

1 个答案:

答案 0 :(得分:5)

这是calling a non-final method in a constructor的情况,因此访问未初始化的变量。

在构造基类时,仍会积极评估此行:

val processFullName: String = "$moduleName/$packageName.$processName"

要获取这两个惰性属性的值,将调用抽象方法,其中packageName()引用jmsDirection,而cdmCode返回其值-这些属性是尚未初始化,因为它们的值是在之后设置的。这是子类的构造函数的简化版本,已反编译回Java:

public WorkerFullNameBuilder(@NotNull JmsDirectionEnumeration jmsDirection, @NotNull TechnicalDomainEnumeration technicalDomain, @NotNull String cdmCode) {
    super(technicalDomain);
    this.jmsDirection = jmsDirection;
    this.cdmCode = cdmCode;
}

作为演示,例如,如果您不引用它们,例如,如果您在两个子类方法中均返回常量,则您的代码实际上可以正常运行:

override fun packageName() = "foo"
override fun processName() = "Worker"

但是,您这里需要的解决方案最有可能使processFullName属性本身变得懒惰,而不是它使用的两个值(无论如何,您现在正在构造函数时对其进行评估,因此您没有使懒惰地使用它们)。这意味着您甚至不需要将这两个作为单独的属性:

abstract class SubProcessFullNameBuilder(technicalDomain: TechnicalDomainEnumeration) {

    protected val moduleName = "td.${technicalDomain.value().toLowerCase()}.shared"

    val processFullName by lazy { "$moduleName/${packageName()}.${processName()}" }

    protected abstract fun packageName(): String
    protected abstract fun processName(): String

}