如何获取给定密封类的所有子类?

gle*_*e8e 30 kotlin

最近我们将我们的一个枚举类升级为带有对象作为子类的密封类,因此我们可以进行另一层抽象来简化代码.但是我们不能再通过Enum.values()函数获取所有可能的子类,这很糟糕,因为我们非常依赖于该功能.有没有办法用反射或任何其他工具检索此类信息?

PS:手动将它们添加到阵列是不可接受的.目前有45个,并计划增加更多.


这就是我们的密封类的样子:

sealed class State

object StateA: State()
object StateB: State()
object StateC: State()
....// 42 more
Run Code Online (Sandbox Code Playgroud)

如果有值集合,它将采用以下形状:

val VALUES = setOf(StateA, StateB, StateC, StateC, StateD, StateE,
    StateF, StateG, StateH, StateI, StateJ, StateK, StateL, ......
Run Code Online (Sandbox Code Playgroud)

当然没有人想要保持这样的怪物.

mfu*_*n26 43

在Kotlin 1.3+中你可以使用sealedSubclasses.

在以前的版本中,如果将子类嵌套在基类中,则可以使用nestedClasses:

Base::class.nestedClasses
Run Code Online (Sandbox Code Playgroud)

如果在基类中嵌套其他类,则需要添加过滤.例如:

Base::class.nestedClasses.filter { it.isFinal && it.isSubclassOf(Base::class) }
Run Code Online (Sandbox Code Playgroud)

请注意,这为您提供了子类,而不是这些子类的实例(与此不同Enum.values()).


使用您的特定示例,如果您的所有嵌套类State都是您的object状态,那么您可以使用以下内容来获取所有实例(例如Enum.values()):

State::class.nestedClasses.map { it.objectInstance as State }
Run Code Online (Sandbox Code Playgroud)

如果你想真正想要的话,你甚至可以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)
}
Run Code Online (Sandbox Code Playgroud)

这使得您可以像其他任何一样调用以下内容Enum:

State.valueOf("StateB")
State.values()
enumValueOf<State>("StateC")
enumValues<State>()
Run Code Online (Sandbox Code Playgroud)

UPDATE

EnumKotlin不再支持直接扩展.请参见 禁止显式扩展Enum类:KT-7773.

  • 对于那些像我一样无法立即弄清楚 `isSubclassOf` 来自哪里的人 - 它是 kotlin 反射 api。将 `compile "org.jetbrains.kotlin:kotlin-reflect:1.1.4-3"` 添加到你的 build.gradle 依赖项 (2认同)
  • @HendraAnggrian,显然从 Kotlin 1.1.4 开始这不再可能了。:-( (2认同)
  • @mfulton26 感谢您的澄清。虽然很遗憾,但这是一个巧妙的技巧。 (2认同)

Nar*_*pai 9

完整示例:

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

}
Run Code Online (Sandbox Code Playgroud)


Rom*_* F. 7

使用 Kotlin 1.3+,您可以使用反射列出所有密封的子类,而无需使用嵌套类:https ://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-class/sealed-subclasses .html

我要求提供一些功能来实现相同的功能而无需反思:https : //discuss.kotlinlang.org/t/list-of-sealed-class-objects/10087


hol*_*ava 5

一个明智的选择是在 kotlin 中使用ServiceLoader。然后写一些提供者来获取一个通用的类、枚举、对象或数据类实例。例如:

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

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

fun YourSealedClassProvider.get():List<SealedClass>{/*todo*/};
Run Code Online (Sandbox Code Playgroud)

层次结构如下:

                Provider                    SealedClass
                   ^                             ^
                   |                             |
            --------------                --------------
            |            |                |            |
        EnumProvider ObjectProvider    ObjectClass  EnumClass
            |            |-------------------^          ^
            |                    <uses>                 |
            |-------------------------------------------|
                                 <uses>
Run Code Online (Sandbox Code Playgroud)

另一种选择更复杂,但它可以满足您的需求,因为在同一个包中密封了类。让我告诉你如何以这种方式存档:

  1. 获取密封类的 URL,例如: ClassLoader.getResource("com/xxx/app/YourSealedClass.class")
  2. 扫描密封类 URL 的父级中的所有 jar 条目/目录文件,例如:jar://**/com/xxx/appor file://**/com/xxx/app,然后找出所有"com/xxx/app/*.class"文件/条目。
  3. 使用加载过滤的类 ClassLoader.loadClass(eachClassName)
  4. 检查加载的类是否是密封类的子类
  5. 决定如何获取子类实例,例如:Enum.values(), object.INSTANCE
  6. 返回已建立的密封类的所有实例