How to obtain all subclasses of a given sealed class?

In Kotlin 1.3+ you can use sealedSubclasses.

In prior versions, if you nest the subclasses in your base class then you can use nestedClasses:

Base::class.nestedClasses

If you nest other classes within your base class then you'll need to add filtering. e.g.:

Base::class.nestedClasses.filter { it.isFinal && it.isSubclassOf(Base::class) }

Note that this gives you the subclasses and not the instances of those subclasses (unlike Enum.values()).


With your particular example, if all of your nested classes in State are your object states then you can use the following to get all of the instances (like Enum.values()):

State::class.nestedClasses.map { it.objectInstance as State }

And if you want to get really fancy you can even extend Enum<E: Enum<E>> and create your own class hierarchy from it to your concrete objects using reflection. e.g.:

sealed class State(name: String, ordinal: Int) : Enum<State>(name, ordinal) {
    companion object {
        @JvmStatic private val map = State::class.nestedClasses
                .filter { klass -> klass.isSubclassOf(State::class) }
                .map { klass -> klass.objectInstance }
                .filterIsInstance<State>()
                .associateBy { value -> value.name }

        @JvmStatic fun valueOf(value: String) = requireNotNull(map[value]) {
            "No enum constant ${State::class.java.name}.$value"
        }

        @JvmStatic fun values() = map.values.toTypedArray()
    }

    abstract class VanillaState(name: String, ordinal: Int) : State(name, ordinal)
    abstract class ChocolateState(name: String, ordinal: Int) : State(name, ordinal)

    object StateA : VanillaState("StateA", 0)
    object StateB : VanillaState("StateB", 1)
    object StateC : ChocolateState("StateC", 2)
}

This makes it so that you can call the following just like with any other Enum:

State.valueOf("StateB")
State.values()
enumValueOf<State>("StateC")
enumValues<State>()

UPDATE

Extending Enum directly is no longer supported in Kotlin. See Disallow to explicitly extend Enum class : KT-7773.


Full example:

sealed class State{
    companion object {
        fun find(state: State) =
            State::class.sealedSubclasses
                    .map { it.objectInstance as State}
                    .firstOrNull { it == state }
                    .let {
                        when (it) {
                            null -> UNKNOWN
                            else -> it
                        }
                    }
    }
    object StateA: State()
    object StateB: State()
    object StateC: State()
    object UNKNOWN: State()

}

With Kotlin 1.3+ you can use reflection to list all sealed sub-classes without having to use nested classes: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-class/sealed-subclasses.html

I asked for some feature to achieve the same without reflection: https://discuss.kotlinlang.org/t/list-of-sealed-class-objects/10087

Tags:

Kotlin