Как получить все подклассы данного закрытого класса?
Недавно мы обновили один из наших классов 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