Initialization order of companion object and initializers in an enum


#1

I was trying to write an enum, where in the enum initializer some values are stored in a map in a companion object.
However, the in the initializer function, the map is still NULL, thus I get a NPE while the enum instances are created.

Sample code is:

enum class Test(vararg mappedValues : String) {
    T1("val1.1", "val1.2"),
    T2("val2")
    ;

    companion object {
        private val valueMapping = mutableMapOf<String,Test>()

        fun fromMappedValue(mappedValue : String) : Test {
            return valueMapping[mappedValue] ?: throw IllegalArgumentException("Illegal mapped value: $mappedValue")
        }
    }

    init {
        mappedValues.forEach { valueMapping[it] = this }
    }
}

fun main(args: Array<String>) {
    val v2 = "val2"
    println("'$v2' is mapped to ${Test.fromMappedValue(v2)}")
}

And the exception which I get is:

Exception in thread "main" java.lang.ExceptionInInitializerError
	at EnumInitOptionKt.main(enumInitOption.kt:21)
Caused by: java.lang.NullPointerException
	at Test$Companion.access$getValueMapping$p(enumInitOption.kt:6)
	at Test.<init>(enumInitOption.kt:15)
	at Test.<clinit>(enumInitOption.kt:2)
	... 1 more

Process finished with exit code 1

To me this exception is not logical, and I don’t see how to get rid of it, other than by moving the private valueMapping field out of the enum definition (but I’d rather keep it together).
So this looks like a bug to me?

Since it’s an enum, I cannot move the companion object to above the first constructor call.

Kotlin version: 1.1.1


#2

How about making valueMapping lazily initialized?

private val valueMapping: MutableMap <String, Test> by lazy {mutableMapOf<String,Test>()}

#3

Yeah that might work, it does still sound like a workaround though.


#4

I ran into the same issue, with Java code converted by Idea.

enum TestEnum {
    SOME_VALUE("world");

    String value;

    private static final USEFUL_CONSTANT = "Hello ";

   TestEnum(String value) {
       this.value = USEFUL_CONSTANT + value; // NPE in translated code
   }
}

#5

If you need a simple primitive constant you can use const:

enum class TestEnum {
    SOME_VALUE("world");

    val value: String

    companion object {
        const val USEFUL_CONSTANT = "Hello "
    }

    constructor(value: String) {
        this.value = USEFUL_CONSTANT + value
    }
}