Effective Enums in Kotlin with reverse lookup?

后端 未结 12 1678
野性不改
野性不改 2020-11-30 01:40

I\'m trying to find the best way to do a \'reverse lookup\' on an enum in Kotlin. One of my takeaways from Effective Java was that you introduce a static map inside the enum

相关标签:
12条回答
  • 2020-11-30 02:10

    Based on your example, i might suggest removing the associated value and just use the ordinal which is similar to an index.

    ordinal - Returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial constant is assigned an ordinal of zero).

    enum class NavInfoType {
        GreenBuoy,
        RedBuoy,
        OtherBeacon,
        Bridge,
        Unknown;
    
        companion object {
            private val map = values().associateBy(NavInfoType::ordinal)
            operator fun get(value: Int) = map[value] ?: Unknown
        }
    }
    

    In my case i wanted to return Unknown if the map returned null. You could also throw an illegal argument exception by replacing the get with the following:

    operator fun get(value: Int) = map[value] ?: throw IllegalArgumentException()
    
    0 讨论(0)
  • 2020-11-30 02:12

    It makes not much sense in this case, but here is a "logic extraction" for @JBNized's solution:

    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
    

    In general that's the thing about companion objects that they can be reused (unlike static members in a Java class)

    0 讨论(0)
  • 2020-11-30 02:12

    I found myself doing the reverse lookup by custom, hand coded, value couple of times and came of up with following approach.

    Make enums implement a shared interface:

    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
    }
    

    This interface (however strange the name is :)) marks a certain value as the explicit code. The goal is to be able to write:

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

    Which can easily be achieved with the following code:

    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)
    
    0 讨论(0)
  • 2020-11-30 02:14

    we can use find which Returns the first element matching the given predicate, or null if no such element was found.

    companion object {
       fun valueOf(value: Int): Type? = Type.values().find { it.value == value }
    }
    
    0 讨论(0)
  • 2020-11-30 02:14

    A variant of some previous proposals might be the following, using ordinal field and 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)
        }
    }
    

    }

    0 讨论(0)
  • 2020-11-30 02:16

    A slightly extended approach of the accepted solution with null check and invoke function

    fun main(args: Array<String>) {
        val a = Type.A // find by name
        val anotherA = Type.valueOf("A") // find by name with Enums default valueOf
        val aLikeAClass = Type(3) // find by value using invoke - looks like object creation
    
        val againA = Type.of(3) // find by value
        val notPossible = Type.of(6) // can result in null
        val notPossibleButThrowsError = Type.ofNullSave(6) // can result in IllegalArgumentException
    
        // prints: A, A, 0, 3
        println("$a, ${a.name}, ${a.ordinal}, ${a.value}")
        // prints: A, A, A null, java.lang.IllegalArgumentException: No enum constant Type with value 6
        println("$anotherA, $againA, $aLikeAClass $notPossible, $notPossibleButThrowsError")
    }
    
    enum class Type(val value: Int) {
        A(3),
        B(4),
        C(5);
    
        companion object {
            private val map = values().associateBy(Type::value)
            operator fun invoke(type: Int) = ofNullSave(type)
            fun of(type: Int) = map[type]
            fun ofNullSave(type: Int) = map[type] ?: IllegalArgumentException("No enum constant Type with value $type")
        }
    }
    
    0 讨论(0)
提交回复
热议问题