Elye
Elye

Reputation: 60081

Enum value became null when cross referencing. How to solve this issue?

I have 2 enums that cross-reference each other

enum class School(val zone: String, val captain: Student) {
    Metricon("New York", Student.David),
    Carlisle("London", Student.Paul)
}

enum class Student(val lastName: String, val school: School) {
    David("Samuel", School.Metricon),
    Solomon("Handsome", School.Metricon),
    Saul("Black", School.Metricon),
    Paul("Lewis", School.Carlisle),
    Joseph("Hardy", School.Carlisle),
    John("Baptise", School.Carlisle),
}

When I print it out

        val student = Student.David
        val school = School.Carlisle

        println(student.school)
        println(school.captain)
        println(student.lastName)
        println(school.zone)

It resulted in

Metricon
null
Samuel
London

Notice the null there. The school.captain is missing and became null.

How can I solve this problem?

Upvotes: 4

Views: 668

Answers (1)

Sweeper
Sweeper

Reputation: 270995

The reason for this is specified in the Kotlin Language Specification - Classifier Initialisation, after talking about the exact steps in which initialisation takes place,

If any step in the initialization order creates a loop, it results in unspecified behaviour.

When initialising any of the Student enum entries, a School enum entry would need to initialised (as you used those as arguments for the constructor), and when initialising a School, a Student needs to be initialised (for the same reason), hence forming a loop. According to the spec, this is unspecified behaviour, and each platform can do its own thing.

There are many ways around this.

One way could be to put the enum parameters into lambdas so that they are lazily evaluated:

enum class School(val zone: String, private val _captain: () -> Student) {
    Metricon("New York", { Student.David }),
    Carlisle("London", { Student.Paul });

    val captain get() = _captain()
}

enum class Student(val lastName: String, private val _school: () -> School) {
    David("Samuel", { School.Metricon }),
    Solomon("Handsome", { School.Metricon }),
    Saul("Black", { School.Metricon }),
    Paul("Lewis", { School.Carlisle }),
    Joseph("Hardy", { School.Carlisle }),
    John("Baptise", { School.Carlisle });

    val school get() = _school()
}

Another idea would be to use an abstract val instead, but this is more verbose:

enum class School(val zone: String) {
    Metricon("New York") { override val captain get() = Student.David },
    Carlisle("London") { override val captain get() = Student.Paul };

    abstract val captain: Student
}

enum class Student(val lastName: String) {
    David("Samuel") { override val school get() = School.Metricon},
    Solomon("Handsome") { override val school get() = School.Metricon },
    Saul("Black") { override val school get() = School.Metricon },
    Paul("Lewis") { override val school get() = School.Carlisle },
    Joseph("Hardy") { override val school get() = School.Carlisle },
    John("Baptise") { override val school get() = School.Carlisle };

    abstract val school: School
}

Finally, for your specific situation, you can also change your design to include a isCaptain flag in the Students instead.

enum class Student(val lastName: String, val school: School, val isCaptain: Boolean = false) {
    David("Samuel", School.Metricon, isCaptain = true),
    Solomon("Handsome", School.Metricon),
    Saul("Black", School.Metricon),
    Paul("Lewis", School.Carlisle, isCaptain = true),
    Joseph("Hardy", School.Carlisle),
    John("Baptise", School.Carlisle),
}

Then in you can do a linear search to find out who's captain.

// in School
val captain: Student get() = Student.values().first { it.school == this && it.isCaptain }

Side note: Are you trying to create a database using enum entries? Enums are not the suitable tool for this. Use an actual database instead.

Upvotes: 5

Related Questions