2016-06-13 14 views
28

Ich versuche den besten Weg zu finden, eine 'Reverse Lookup' auf einem Enum in Kotlin zu machen. Einer meiner Vorteile von Effective Java war, dass Sie eine statische Map innerhalb der Enumeration einführen, um die umgekehrte Suche zu handhaben. Portieren dieses über Kotlin mit einem einfachen Enum führt mich zu Code, der wie folgt aussieht:Effektive Enums in Kotlin mit Reverse Lookup?

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

Meine Frage ist, ist dies der beste Weg, dies zu tun, oder gibt es einen besseren Weg? Was ist, wenn ich mehrere Enums habe, die einem ähnlichen Muster folgen? Gibt es einen Weg in Kotlin, um diesen Code in Enums wiederverwendbar zu machen?

Antwort

50

Zunächst sollte das Argument von fromInt() ein Int sein, nicht ein Int ?. Der Versuch, einen Typ mit null zu erhalten, führt offensichtlich zu null, und ein Aufrufer sollte dies nicht einmal versuchen. Die Karte hat auch keinen Grund, veränderbar zu sein. Der Code kann

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

Dieser Code zu reduzieren ist so kurz, dass, ehrlich gesagt, ich bin nicht sicher, dass es einen Versuch wert, eine wieder verwendbare Lösung zu finden.

+0

Ich war dabei, das gleiche zu empfehlen. Außerdem würde ich 'fromInt' nicht-null zurückgeben wie' Enum.valueOf (String) ':' map [type]?: Throw IllegalArgumentException() ' – mfulton26

+2

Angesichts der Kotlin-Unterstützung für Null-Sicherheit, Rückgabe NULL von der Methode würde mich nicht stören, wie es in Java der Fall wäre: Der Aufrufer wird vom Compiler gezwungen, sich mit einem Null-Rückgabewert zu befassen und zu entscheiden, was zu tun ist (werfen oder etwas anderes tun). –

+0

Guter Punkt. Vielen Dank. – mfulton26

5

Ich fand mich die Reverse-Lookup von benutzerdefinierten, handkodierten, Wert paar Male und kamen mit folgenden Ansatz.

Stellen enum s eine gemeinsame Schnittstelle implementieren:

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 
} 

Diese Schnittstelle (so seltsam der Name ist :)) einen bestimmten Wert als expliziter Code markiert. Das Ziel ist es, schreiben:

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

, die leicht mit dem folgenden Code erreicht werden kann:

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) 
+2

Das ist eine Menge Arbeit für eine so einfache Operation, die angenommene Antwort ist viel sauberer IMO –

+1

Stimme völlig für die einfache Verwendung ist es definitiv besser. Ich hatte den obigen Code bereits, um explizite Namen für bestimmte aufgeführte Member zu behandeln. – miensol

+0

Ich bin erstaunt, wie mit dieser Lösung kommen. Ich denke, das sollte in der Stander-Bibliothek sein. oder etwas wie Guave oder Apache commons, aber für Kotlin. – humazed

10

Es macht nicht viel Sinn, in diesem Fall, aber hier ist eine „logische Extraktion“ für @ JBNized Lösung:

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 

im allgemeinen ist das die Sache über Begleiter Objekte, dass sie (im Gegensatz zu statischen Mitgliedern in einer Java-Klasse) wiederverwendet werden können

-2

val t = Type.values ​​() [Ordnungs]

:)

2

wir find die Gibt das erste Element Anpassen des gegebenen Prädikat, oder null, wenn kein solches Element gefunden wurde, verwenden können.

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