代码之家  ›  专栏  ›  技术社区  ›  glee8e

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

  •  72
  • glee8e  · 技术社区  · 7 年前

    最近,我们将一个枚举类升级为密封类 因此,我们可以进行另一层抽象来简化代码。然而,我们不能再通过所有可能的子类 Enum.values() 功能,这是不好的,因为我们严重依赖于该功能。有没有办法用反射或任何其他工具检索此类信息?

    不可接受的


    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, ......
    

    6 回复  |  直到 7 年前
        1
  •  89
  •   mfulton26    6 年前

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

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

    Base::class.nestedClasses
    

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

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

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


    State 是你的 object 枚举值() ):

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

    Enum<E: Enum<E>> 并使用 reflection 例如:

    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中不再支持直接。看见 Disallow to explicitly extend Enum class : KT-7773

        2
  •  8
  •   Romain F.    6 年前

    使用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
  •  5
  •   Naruto Sempai    5 年前

    完整示例:

    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()
    
    }
    
        4
  •  5
  •   Community CDub    4 年前

    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
                |            |-------------------^          ^
                |                    <uses>                 |
                |-------------------------------------------|
                                     <uses>
    

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

    1. 获取密封类的URL,例如: ClassLoader.getResource("com/xxx/app/YourSealedClass.class")
    2. jar://**/com/xxx/app file://**/com/xxx/app ,然后找出所有 "com/xxx/app/*.class" 文件/条目。
    3. 使用加载筛选类 ClassLoader.loadClass(eachClassName)
    4. 检查加载的类是否是密封类的子类
    5. 决定如何获取子类实例,例如: Enum.values() object.INSTANCE .
        5
  •  2
  •   zzycami    3 年前

    如果你想在儿童课上使用它,试试这个。

    open class BaseSealedClass(val value: String, val name: Int) {
        companion object {
            inline fun<reified T:BaseSealedClass> valueOf(value: String): T? {
                return T::class.nestedClasses
                    .filter { clazz -> clazz.isSubclassOf(T::class) }
                    .map { clazz -> clazz.objectInstance }
                    .filterIsInstance<T>()
                    .associateBy { it.value }[value]
            }
    
            inline fun<reified  T:BaseSealedClass> values():List<T> =
                T::class.nestedClasses
                .filter { clazz -> clazz.isSubclassOf(T::class) }
                .map { clazz -> clazz.objectInstance }
                .filterIsInstance<T>()
        }
    }
    
    @Stable
    sealed class Theme(value: String, name: Int): BaseSealedClass(value, name) {
        object Auto: Theme(value = "auto", name = R.string.setting_general_theme_auto)
        object Light: Theme(value= "light", name = R.string.setting_general_theme_light)
        object Dark: Theme(value= "dark", name = R.string.setting_general_theme_dark)
    
        companion object {
            fun valueOf(value: String): Theme? = BaseSealedClass.valueOf(value)
            fun values():List<Theme> = BaseSealedClass.values()
        }
    }
    
    
    
        6
  •  0
  •   Sebas LG    3 年前

    对于没有反射的解决方案,这是一个库,支持在编译时生成密封类的类型列表: https://github.com/livefront/sealed-enum

    sealed class Alpha {
        object Beta : Alpha()
        object Gamma : Alpha()
        
        @GenSealedEnum
        companion object
    }
    

    将生成以下对象:

    object AlphaSealedEnum : SealedEnum<Alpha> {
        override val values: List<Alpha> = listOf(
            Alpha.Beta,
            Alpha.Gamma
        )
    
        override fun ordinalOf(obj: Alpha): Int = when (obj) {
            Alpha.Beta -> 0
            Alpha.Gamma -> 1
        }
    
        override fun nameOf(obj: AlphaSealedEnum): String = when (obj) {
            Alpha.Beta -> "Alpha_Beta"
            Alpha.Gamma -> "Alpha_Gamma"
        }
    
        override fun valueOf(name: String): AlphaSealedEnum = when (name) {
            "Alpha_Beta" -> Alpha.Beta
            "Alpha_Gamma" -> Alpha.Gamma
            else -> throw IllegalArgumentException("""No sealed enum constant $name""")
        }
      }