在 Kotlin 中迭代枚举值的泛型方法接口

Interface with generic method iterating over enum values in Kotlin

假设这些接口

interface RatingValue {
    val value: Int
}

interface RatingValues {
    fun ratings() : List<Int>
    fun cumulativeRating() : Int = ratings().sum()
}

还有一堆类似于这个的枚举

enum class MarkFoo(override val value: Int) : RatingValue {
        EXCELENT(5),
        VERY_GOOD(4),
        GOOD(3),
        FAIR(2),
        POOR(1);

        companion object : RatingValues {
            override fun ratings() = values().map(MarkFoo::value)
        }
    }

有没有一种方法可以将 ratings() 函数的实现一般地提取到 RatingValues 接口中,以避免在所有枚举的同伴中具有基本相同的精确实现?每个枚举都将实现 RatingValue,并且每个枚举都有一个实现 RatingValues

的同伴

您需要生成 RatingValues 接口以从中访问 Enum<T>RatingValue 的方法。 但是由于具体化的泛型不允许用于接口甚至它们的虚拟成员,因此您必须将这些方法转换为扩展:

interface RatingValues<T>
inline fun <reified T> RatingValues<T>.ratings(): List<Int> where T : RatingValue, T : Enum<T> = enumValues<T>().map { it.value }
inline fun <reified T> RatingValues<T>.cumulativeRating(): Int where T : RatingValue, T : Enum<T> = ratings().sum()

用法:

enum class MarkFoo(override val value: Int) : RatingValue {
    EXCELENT(5),
    VERY_GOOD(4),
    GOOD(3),
    FAIR(2),
    POOR(1);

    companion object : RatingValues<MarkFoo>
}

问题是这些方法不能同时声明为抽象接口成员,因为在这种情况下扩展会被隐藏。因此,现在只能针对特定的 Ts(枚举,实现 RatingValue 接口)在此接口上调用它们,并且不能被覆盖。对于其他类型,接口将是空的,没有任何方法可以覆盖:

class X(override val value: Int) : RatingValue
class XX : RatingValues<X> {
    override fun ratings(): List<Int> { /*...*/ } //Impossible now
    override fun cumulativeRating(): Int { /*...*/ } //Impossible now
}

val res = XX.ratings() // Will not compile now since X is not enum

如果您也打算以上述方式使用此接口,那么您必须像以前一样声明接口,只添加一个扩展名但名称不同(如 ratingsForEnums),并且每次都添加一些样板你为枚举实现这个接口:

interface RatingValues<T> {
    fun ratings(): List<Int>
    fun cumulativeRating(): Int = ratings().sum()
}

inline fun <reified T> RatingValues<T>.ratingsForEnums(): List<Int> where T : RatingValue, T : Enum<T> = enumValues<T>().map { it.value }

enum class MarkFoo(override val value: Int) : RatingValue {
    EXCELENT(5),
    VERY_GOOD(4),
    GOOD(3),
    FAIR(2),
    POOR(1);

    companion object : RatingValues<MarkFoo> {
        override fun ratings() = ratingsForEnums()
    }
}

如果您打算仅将此接口与枚举进行对比,那么也可以在接口级别明确地对 T 施加限制:

interface RatingValues<T> where T : RatingValue, T : Enum<T>