रिवर्स लुकअप के साथ कोटलिन में प्रभावी एनम?


103

मैं कोटलिन के एक एनम पर 'रिवर्स लुकअप' करने का सबसे अच्छा तरीका खोजने की कोशिश कर रहा हूं। प्रभावी जावा से मेरा takeaways में से एक यह था कि आप रिवर्स लुकअप को संभालने के लिए Enum के अंदर एक स्थिर नक्शा पेश करते हैं। कोटलिन को एक साधारण एनम के साथ इस तरह से पोर्ट करना मुझे ऐसे कोड की ओर ले जाता है जो इस तरह दिखता है:

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]
        }
    }
}

मेरा सवाल यह है कि क्या ऐसा करने का यह सबसे अच्छा तरीका है, या क्या बेहतर तरीका है? क्या होगा अगर मेरे पास कई एनम हैं जो समान पैटर्न का पालन करते हैं? क्या कोटलिन में एक तरीका है जिससे इस कोड को एनम के पार फिर से उपयोग किया जा सके?


आपका Enum आईडी प्रॉपर्टी के साथ पहचाने जाने योग्य इंटरफ़ेस को लागू करना चाहिए और साथी ऑब्जेक्ट को अमूर्त वर्ग GettableById का विस्तार करना चाहिए जो idToEnumValue मैप रखता है और आईडी के आधार पर एनम वैल्यू देता है। मेरे जवाब में विवरण नीचे है।
एल्डारल एग्रोव

जवाबों:


177

सबसे पहले, का तर्क ए fromInt()होना चाहिए , ए Intनहीं Int?। एक Typeअशक्त नल का उपयोग करने की कोशिश करने से स्पष्ट रूप से अशक्त हो जाएगा, और एक कॉलर को भी ऐसा करने की कोशिश नहीं करनी चाहिए। 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
अशक्त-सुरक्षा के लिए कोटलिन समर्थन को देखते हुए, विधि से अशक्त लौट आना मुझे परेशान नहीं करेगा क्योंकि यह जावा में होगा: कॉल करने वाले को संकलक द्वारा एक अशक्त लौटे मूल्य से निपटने के लिए मजबूर किया जाएगा, और तय करना होगा कि क्या करना है (फेंकना या करना कुछ और)।
जेबी निज़ेट

1
@ राफेल क्योंकि जावा 5 में एनमों को पेश किया गया था और जावा 8. में वैकल्पिक
जेबी निज़ेट

2
इस कोड के मेरे संस्करण के by lazy{}लिए mapऔर उपयोग के getOrDefault()लिए सुरक्षितvalue
Hoang Tran

2
यह समाधान अच्छी तरह से काम करता है। ध्यान दें कि Type.fromInt()जावा कोड से कॉल करने में सक्षम होने के लिए , आपको विधि को एनोटेट करना होगा @JvmStatic
आर्टो बेंडिकेन

35

यदि कोई ऐसा तत्व नहीं मिला, तो हम पहले दिए गए तत्व का मिलान कर सकते हैं findजो दिए गए विधेय, या अशक्त से मेल खाते हैं।

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

4
एक स्पष्ट वृद्धि first { ... }इसके बजाय उपयोग कर रही है क्योंकि कई परिणामों के लिए कोई उपयोग नहीं है।
क्रिएटिव

9
नहीं, उपयोग firstकरना एक वृद्धि नहीं है क्योंकि यह व्यवहार को बदलता है और फेंकता है NoSuchElementExceptionयदि आइटम नहीं मिला है findजो कि firstOrNullरिटर्न के बराबर है null। इसलिए यदि आप अशक्त उपयोग को वापस करने के बजाय फेंकना चाहते हैंfirst
०१

इस विधि का उपयोग कई मानों के साथ enums के साथ किया जा सकता है: 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 में लुकअप का उपयोग करने के लिए बेहतर है।
एल्डारॉलाव

हां, मुझे पता है लेकिन ज्यादातर मामलों में, एनम के पास बहुत कम संख्या में राज्य होंगे, इसलिए यह किसी भी तरह से मायने नहीं रखता है, क्या अधिक पठनीय है।
humazed

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

सामान्य तौर पर साथी वस्तुओं के बारे में यह बात है कि उनका पुन: उपयोग किया जा सकता है (जावा वर्ग में स्थिर सदस्यों के विपरीत)


आप खुली कक्षा का उपयोग क्यों करते हैं? बस इसे अमूर्त बनाओ।
एल्डारॉलाव

21

एक और विकल्प, जिसे अधिक "मुहावरेदार" माना जा सकता है, वह निम्नलिखित होगा:

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

जिसे बाद में इस्तेमाल किया जा सकता है Type[type]


निश्चित रूप से अधिक मुहावरेदार! चीयर्स।
AleksandrH

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
}

यह इंटरफ़ेस (हालांकि नाम अजीब है :)) स्पष्ट कोड के रूप में एक निश्चित मूल्य को चिह्नित करता है। लक्ष्य लिखने में सक्षम होना है:

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
इस तरह के एक सरल ऑपरेशन के लिए बहुत काम है, स्वीकृत जवाब बहुत साफ है आईएमओ
कॉनर व्याट

2
सरल उपयोग के लिए पूरी तरह से सहमत यह निश्चित रूप से बेहतर है। मेरे पास उपरोक्त कोड पहले से ही दिए गए सदस्य के लिए स्पष्ट नामों को संभालने के लिए था।
मिलेन्सॉल

प्रतिबिंब (खराब) का उपयोग कर आपका कोड और फूला हुआ (बुरा भी) है।
एल्डारेलोवो

1

पिछले कुछ प्रस्तावों का एक प्रकार निम्नलिखित हो सकता है, अध्यादेश क्षेत्र और 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

वैल टी = टाइप.वेल्यूज़ () [क्रम]

:)


यह स्थिरांक 0, 1, ..., N के लिए काम करता है। यदि आपके पास 100, 50, 35 की तरह है, तो यह एक सही परिणाम नहीं देगा।
कूलमैन्ड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.