Hi,
I am wondering what the proper Kotlin way would be to handle situations like these:
sealed interface Year {
companion object {
val Y2023 = ConcreteYear(2023)
val Y2024 = ConcreteYear(2024)
val UNKNOWN = UnknownYear()
}
}
class ConcreteYear(val year: Int): Year
class UnknownYear(): Year
Basically, I would like to limit the instances for Year. That is, that the only place that those instances can be created should be at one central location (here in the companion object).
How does this differ in its byte code implementation from something like that?
public abstract class JavaYear {
public static JavaYear Y2025 = new ConcreteJavaYear();
^^^^^^^^^^^^^^^^^^^^^^
public static class ConcreteJavaYear extends JavaYear {
}
}
IDEA shows a warning for the instantiation: Referencing subclass ConcreteJavaYear from superclass JavaYear initializer might lead to class loading deadlock.
I would expect that the Kotlin version would run the same risks?
Kotlin doesn’t create fields in JavaYear. When we do JavaYear.Y2024, we don’t get a Y2024 field of JavaYear class. We get a singleton object JavaYear.Y2024. Java doesn’t have a concept of singleton objects, so this is not directly translatable to it.
sealed interface Year {
object Y2023 : ConcreteYear(2023)
object Y2024 : ConcreteYear(2024)
object UNKNOWN : Year
}
sealed class ConcreteYear(val year: Int): Year
In Java that looks like Year.Y2023.INSTANCE. In other words, there is a field involved but with an indirection. Or is that indirection already enough to avoid the class loader deadlock? It could be, because the instance is created in another class, couldn’t it be?
The problem in your Java code was that JavaYear in its static constructor calls the constructor of ConcreteJavaYear and as ConcreteJavaYear is a subtype of JavaYear, then it has to call the JavaYear back. In Kotlin, JavaYear can be initialized without initializing its subtype Y2024, so this problem doesn’t exist.
You can do the same in Java, but you will have to acquire Y2024 with either: JavaYear.Y2024.INSTANCE or JavaYear.getY2024().
You are right! My oversight is that the culprit is the constructor call and not that the sub class is referenced. That is indeed a completely different scenario from the internal Kotlin implementation.