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

时间:2017-06-27 15:32:03

标签: kotlin

最近我们将我们的一个枚举类升级为密封类,并将对象作为子类,因此我们可以进行另一层抽象来简化代码。但是我们不能再通过sealed class State object StateA: State() object StateB: State() object StateC: State() ....// 42 more 函数获取所有可能的子类,这很糟糕,因为我们严重依赖于该功能。有没有办法用反射或任何其他工具检索此类信息?

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

这就是我们的密封课程的样子:

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

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

{{1}}

当然没有人愿意维持这样一个怪物。

4 个答案:

答案 0 :(得分:38)

在Kotlin 1.3+中,您可以使用sealedSubclasses

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

Base::class.nestedClasses

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

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

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

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

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

如果你想真正想象,你甚至可以使用reflection扩展Enum<E: Enum<E>>并使用它创建自己的类层次结构到具体对象。 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)
}

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

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

<强>更新

Kotlin不再支持直接扩展Enum。看到 Disallow to explicitly extend Enum class : KT-7773

答案 1 :(得分:4)

明智的选择是在kotlin中使用ServiceLoader。然后编写一些提供程序来获取公共类,枚举,对象或数据类实例。例如:

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>

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

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

答案 2 :(得分:3)

使用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

答案 3 :(得分:1)

完整示例:

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

}