Как получить все подклассы данного закрытого класса?

Недавно мы обновили один из наших классов enum до закрытого класса с объектами в качестве подклассов, чтобы мы могли сделать еще один уровень абстракции для упрощения кода. Однако мы не можем получить все возможные подклассы через функцию Enum.values(), что плохо, потому что мы в значительной степени полагаемся на эту функциональность. Есть ли способ получить такую ​​информацию с отражением или любым другим инструментом?

PS: добавление их в массив вручную неприемлемо. В настоящее время их 45, и есть планы добавить еще.


Вот как выглядит наш закрытый класс:

sealed class State

object StateA: State()
object StateB: State()
object StateC: State()
....// 42 more

Если есть коллекция значений, она будет в этой форме:

val VALUES = setOf(StateA, StateB, StateC, StateC, StateD, StateE,
    StateF, StateG, StateH, StateI, StateJ, StateK, StateL, ......

Естественно, никто не хочет поддерживать такого монстра.

Ответ 1

В Kotlin 1. 3+ вы можете использовать sealedSubclasses.

В предыдущих версиях, если вы nestedClasses подклассы в базовый класс, вы можете использовать nestedClasses:

Base::class.nestedClasses

Если вы вложите другие классы в свой базовый класс, вам нужно будет добавить фильтрацию. например:

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

Обратите внимание, что это дает вам подклассы, а не экземпляры этих подклассов (в отличие от Enum.values()).


В вашем конкретном примере, если все ваши вложенные классы в State являются состояниями вашего object вы можете использовать следующее для получения всех экземпляров (например, Enum.values()):

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

И если вы хотите стать по-настоящему модным, вы можете даже расширить Enum<E: Enum<E>> и создать из него собственную иерархию классов для ваших конкретных объектов, используя отражение. например:

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)
}

Это позволяет вам вызывать следующее так же, как и с любым другим Enum:

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

ОБНОВИТЬ

Расширение Enum напрямую больше не поддерживается в Kotlin. См. Disallow, чтобы явно расширить класс Enum: KT-7773.

Ответ 2

Мудрый выбор заключается в использовании ServiceLoader в kotlin. а затем написать некоторые провайдеры, чтобы получить общий класс, перечисление, объект или экземпляр класса данных. например:

val provides = ServiceLoader.load(YourSealedClassProvider.class).iterator();

val subInstances =  providers.flatMap{it.get()};

fun YourSealedClassProvider.get():List<SealedClass>{/*todo*/};

иерархия, как показано ниже:

                Provider                    SealedClass
                   ^                             ^
                   |                             |
            --------------                --------------
            |            |                |            |
        EnumProvider ObjectProvider    ObjectClass  EnumClass
            |            |-------------------^          ^
            |                    <ueses>                |
            |-------------------------------------------|
                         <uses>

Другой вариант, более сложный, но он может удовлетворить ваши потребности, так как запечатанные классы в одном пакете. позвольте мне рассказать вам, как архивировать таким образом:

  • получить URL-адрес вашего закрытого класса, например: ClassLoader.getResource("com/xxx/app/YourSealedClass.class")
  • сканировать все файлы записи/каталога jar в родительском документе с закрытым классом, например: jar://**/com/xxx/app или file://**/com/xxx/app, а затем узнать все файлы и записи "com/xxx/app/*.class".
  • загружать фильтрованные классы с помощью ClassLoader.loadClass(eachClassName)
  • проверить загруженный класс, является ли подкласс вашего закрытого класса
  • решить, как получить экземпляр подкласса, например: Enum.values(), object.INSTANCE.
  • вернуть все экземпляры установленных закрытых классов

Ответ 3

С Kotlin 1. 3+ вы можете использовать отражение, чтобы перечислить все запечатанные подклассы, не используя вложенные классы: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect / -k-class/запечатанное subclasses.html

Я попросил некоторые функции, чтобы достичь того же без размышления: https://discuss.kotlinlang.org/t/list-of-sealed-class-objects/10087