Reputation: 60081
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
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 Student
s 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