Kotlin中有效的Enums反向查找?

Bar*_*ron 87 enums kotlin

我正试图找到在Kotlin上对枚举进行"反向查找"的最佳方法.我对Effective 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]
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

JB *_*zet 152

首先,fromInt()的参数应该是Int,而不是Int?.尝试使用null获取Type显然会导致null,并且调用者甚至不应该尝试这样做.地图也没有理由变得可变.代码可以减少到

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

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

  • 我即将推荐相同的.另外,我会使`fromInt`返回非null,如`Enum.valueOf(String)`:`map [type] ?: throw IllegalArgumentException()` (7认同)
  • 鉴于kotlin对null安全的支持,从方法返回null不会像在Java中那样困扰我:调用者将被编译器强制处理null返回值,并决定做什么(抛出或做什么)别的东西). (4认同)
  • 我的这段代码使用“ by lazy {}”作为“ map”,并使用“ getOrDefault()`”以通过“ value”进行更安全的访问 (2认同)
  • 此解决方案效果很好.注意,为了能够从Java代码调用`Type.fromInt()`,您需要使用`@ JvmStatic`注释该方法. (2认同)

vod*_*dan 25

在这种情况下没有多大意义,但这是@ 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
Run Code Online (Sandbox Code Playgroud)

一般情况下,伴随对象可以重用它们(与Java类中的静态成员不同)

  • 为什么要用公开课?只是让它变得抽象。 (2认同)

hum*_*zed 24

我们可以使用find,如果没有这样的元素被找到,返回的第一个元素匹配给定的谓词,或者为null.

companion object {
   fun valueOf(value: Int): Type? = Type.values().find { it.value == value }
}
Run Code Online (Sandbox Code Playgroud)

  • 不,使用`first`不是增强,因为它改变行为并抛出`NoSuchElementException`如果找不到项,其中`find`等于`firstOrNull`返回`null`.因此,如果你想抛出而不是返回null,请使用`first` (9认同)
  • 一个明显的增强是使用`first {...}`代替,因为没有用于多个结果. (3认同)
  • 是的,我知道,但在大多数情况下,枚举的状态数量非常少,所以无论哪种方式都没有关系,哪个更可读。 (2认同)

squ*_*rel 14

如果您有很多枚举,这可能会节省一些击键次数:

inline fun <reified T : Enum<T>, V> ((T) -> V).find(value: V): T? {
    return enumValues<T>().firstOrNull { this(it) == value }
}
Run Code Online (Sandbox Code Playgroud)

像这样使用它:

enum class Algorithms(val string: String) {
    Sha1("SHA-1"),
    Sha256("SHA-256"),
}

fun main() = println(
    Algorithms::string.find("SHA-256")
            ?: throw IllegalArgumentException("Bad algorithm string: SHA-256")
)
Run Code Online (Sandbox Code Playgroud)

这将打印Sha256


Iva*_*vin 7

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

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

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


mie*_*sol 6

我发现自己通过自定义,手动编码,值几次进行反向查找,并采用以下方法.

enum小号实现共享界面:

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
}
Run Code Online (Sandbox Code Playgroud)

这个接口(不管名称是多么奇怪:))将某个值标记为显式代码.目标是能够写:

val a = Alphabet::class.decode(1) //Alphabet.A
val d = Alphabet::class.tryDecode(4) //null
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

  • 对于如此简单的操作来说,这是很多工作,接受的答案是更清洁的IMO (3认同)
  • 完全同意简单使用它肯定更好。我已经有了上面的代码来处理给定枚举成员的显式名称。 (2认同)

Tor*_*ene 5

另一个示例实现。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
}
Run Code Online (Sandbox Code Playgroud)

}