反向查找在Kotlin中有效的枚举?


102

我正在尝试找到对Kotlin枚举进行“反向查找”的最佳方法。我从有效Java中获得的收获之一是,您在枚举内部引入了静态映射以处理反向查找。使用一个简单的枚举将其移植到Kotlin可使我编写如下代码:

enum class Type(val value: Int) {
    A(1),
    B(2),
    C(3);

    companion object {
        val map: MutableMap<Int, Type> = HashMap()

        init {
            for (i in Type.values()) {
                map[i.value] = i
            } 
        }

        fun fromInt(type: Int?): Type? {
            return map[type]
        }
    }
}

我的问题是,这是最好的方法,还是有更好的方法?如果我有几个遵循类似模式的枚举怎么办?Kotlin中是否有办法使此代码在枚举中更可重用?


您的Enum应该使用id属性实现Identifiable接口,并且伴随对象应该扩展抽象类GettableById,该类包含idToEnumValue映射并基于id返回枚举值。详细信息如下。
Eldar Agalarov

Answers:


175

首先,的参数fromInt()应该是Int,而不是Int?。尝试Type使用null显然会导致null,并且调用方甚至不应尝试这样做。在Map还没有理由是可变的。该代码可以简化为:

companion object {
    private val map = Type.values().associateBy(Type::value)
    fun fromInt(type: Int) = map[type]
}

该代码太短了,坦率地说,我不确定是否值得尝试找到可重用的解决方案。


8
我正要推荐相同的。此外,我将fromInt返回的值Enum.valueOf(String)map[type] ?: throw IllegalArgumentException()
设为

4
鉴于kotlin支持null安全性,从方法中返回null不会像在Java中那样困扰我:编译器将强制调用者处理返回的null值,并确定要执行的操作(抛出或执行其他)。
JB Nizet

1
@Raphael因为枚举被Java 5中引入可选在Java中8
JB Nizet

2
我的代码版本by lazy{}用于- mapgetOrDefault()更安全的访问value
Hoang Tran

2
此解决方案效果很好。请注意,要能够Type.fromInt()从Java代码进行调用,您将需要使用注释该方法@JvmStatic
Arto Bendiken

34

我们可以使用findwhich 返回与给定谓词匹配的第一个元素;如果找不到此类元素,则返回null。

companion object {
   fun valueOf(value: Int): Type? = Type.values().find { it.value == value }
}

4
first { ... }相反,使用了一个明显的增强功能,因为没有使用多个结果。
creativecreatororbenot

9
不,使用first不增强,因为它改变了行为,并抛出NoSuchElementException如果找不到该项目在那里find等于firstOrNull回报null。因此,如果您想抛出而不是返回null使用first
惊讶的是

此方法可以用于具有多个值fun valueFrom(valueA: Int, valueB: String): EnumType? = values().find { it.valueA == valueA && it.valueB == valueB } 的枚举:如果值不在枚举中,也可以引发异常: fun valueFrom( ... ) = values().find { ... } ?: throw Exception("any message") 或者可以在调用此方法时使用它: var enumValue = EnumType.valueFrom(valueA, valueB) ?: throw Exception( ...)
ecth

您的方法具有线性复杂度O(n)。最好在具有O(1)复杂度的预定义HashMap中使用查找。
Eldar Agalarov

是的,我知道,但是在大多数情况下,枚举的状态数量很少,因此无论哪种方式都没有关系,更容易理解。
感到震惊

27

在这种情况下,这没有多大意义,但这是@JBNized解决方案的“逻辑提取”:

open class EnumCompanion<T, V>(private val valueMap: Map<T, V>) {
    fun fromInt(type: T) = valueMap[type]
}

enum class TT(val x: Int) {
    A(10),
    B(20),
    C(30);

    companion object : EnumCompanion<Int, TT>(TT.values().associateBy(TT::x))
}

//sorry I had to rename things for sanity

通常,关于可重复使用的伴随对象的事情(不同于Java类中的静态成员)


为什么要使用公开课?只是使其抽象。
Eldar Agalarov

21

可以认为更“惯用”的另一个选择是:

companion object {
    private val map = Type.values().associateBy(Type::value)
    operator fun get(value: Int) = map[value]
}

然后可以像这样使用Type[type]


绝对更惯用!干杯。
AleksandrH

6

我发现自己通过自定义,手工编码,值几次进行反向查找,并提出了以下方法。

使enums实现一个共享接口:

interface Codified<out T : Serializable> {
    val code: T
}

enum class Alphabet(val value: Int) : Codified<Int> {
    A(1),
    B(2),
    C(3);

    override val code = value
}

该接口(尽管名称很奇怪:))将某个值标记为显式代码。目标是能够编写:

val a = Alphabet::class.decode(1) //Alphabet.A
val d = Alphabet::class.tryDecode(4) //null

可以使用以下代码轻松实现:

interface Codified<out T : Serializable> {
    val code: T

    object Enums {
        private val enumCodesByClass = ConcurrentHashMap<Class<*>, Map<Serializable, Enum<*>>>()

        inline fun <reified T, TCode : Serializable> decode(code: TCode): T where T : Codified<TCode>, T : Enum<*> {
            return decode(T::class.java, code)
        }

        fun <T, TCode : Serializable> decode(enumClass: Class<T>, code: TCode): T where T : Codified<TCode> {
            return tryDecode(enumClass, code) ?: throw IllegalArgumentException("No $enumClass value with code == $code")
        }

        inline fun <reified T, TCode : Serializable> tryDecode(code: TCode): T? where T : Codified<TCode> {
            return tryDecode(T::class.java, code)
        }

        @Suppress("UNCHECKED_CAST")
        fun <T, TCode : Serializable> tryDecode(enumClass: Class<T>, code: TCode): T? where T : Codified<TCode> {
            val valuesForEnumClass = enumCodesByClass.getOrPut(enumClass as Class<Enum<*>>, {
                enumClass.enumConstants.associateBy { (it as T).code }
            })

            return valuesForEnumClass[code] as T?
        }
    }
}

fun <T, TCode> KClass<T>.decode(code: TCode): T
        where T : Codified<TCode>, T : Enum<T>, TCode : Serializable 
        = Codified.Enums.decode(java, code)

fun <T, TCode> KClass<T>.tryDecode(code: TCode): T?
        where T : Codified<TCode>, T : Enum<T>, TCode : Serializable
        = Codified.Enums.tryDecode(java, code)

3
这样简单的操作需要做很多工作,公认的答案是更干净的IMO
Connor Wyatt

2
完全同意简单使用绝对是更好的选择。我已经有了上面的代码来处理给定枚举成员的显式名称。
miensol '16

您的代码使用反射(错误)并且肿(也错误)。
Eldar Agalarov

1

以下是一些先前的建议的变体,使用ordinal field和getValue:

enum class Type {
A, B, C;

companion object {
    private val map = values().associateBy(Type::ordinal)

    fun fromInt(number: Int): Type {
        require(number in 0 until map.size) { "number out of bounds (must be positive or zero & inferior to map.size)." }
        return map.getValue(number)
    }
}

}


1

另一个示例实现。OPEN如果没有输入匹配任何枚举选项,这还将设置默认值(此处为):

enum class Status(val status: Int) {
OPEN(1),
CLOSED(2);

companion object {
    @JvmStatic
    fun fromInt(status: Int): Status =
        values().find { value -> value.status == status } ?: OPEN
}

}


0

提出了更通用的解决方案

inline fun <reified T : Enum<*>> findEnumConstantFromProperty(predicate: (T) -> Boolean): T? =
T::class.java.enumConstants?.find(predicate)

用法示例:

findEnumConstantFromProperty<Type> { it.value == 1 } // Equals Type.A

0

真正的惯用科特林方式。没有肿的反射代码:

interface Identifiable<T : Number> {

    val id: T
}

abstract class GettableById<T, R>(values: Array<R>) where T : Number, R : Enum<R>, R : Identifiable<T> {

    private val idToValue: Map<T, R> = values.associateBy { it.id }

    operator fun get(id: T): R = getById(id)

    fun getById(id: T): R = idToValue.getValue(id)
}

enum class DataType(override val id: Short): Identifiable<Short> {

    INT(1), FLOAT(2), STRING(3);

    companion object: GettableById<Short, DataType>(values())
}

fun main() {
    println(DataType.getById(1))
    // or
    println(DataType[2])
}

-1

val t = Type.values()[ordinal]

:)


这适用于常数0、1,...,N。如果您将其设为100、50、35,则不会给出正确的结果。
CoolMind
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.