简体   繁体   中英

Kotlin lazy initialization in subclass

I'm trying to build a string with properties that are initialized in a subclass.

I read about lazy initialization but somehow this doesn't work as I expected.

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"
}

Since I have overridden the packageName() and processName() properties, I would expect that on calling the packageName property it would use the implementation from the subclass.

But when I call the processFullName property, it throws a java.lang.NullPointerException .

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

How can I initialize the packageName and processName properties in a proper way?

This is a case of calling a non-final method in a constructor and thus accessing uninitialized variables.

This line is still evaluated eagerly, at the time when the base class is constructed:

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

To get the values of the two lazy properties, this will make calls to the abstract methods, of which packageName() refers to jmsDirection and cdmCode to return its value - these properties are not initialized yet, because their values are set after the superclass constructor runs. Here's a simplified version of the subclass' constructor, decompiled back to Java:

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

As a demonstration, if you don't refer to these, for example, if you return constants in both of the subclass methods, your code will actually run fine:

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

However, the solution you need here is most likely to make the processFullName property itself lazy instead of the two values it uses (which you're evaluating at constructor time right now anyway, so you're not making use of them being lazy). This means you don't even need those two as separate properties:

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

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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