2016-06-13 14 views
28

Sto cercando di trovare il modo migliore per eseguire una "ricerca inversa" su un enum in Kotlin. Uno dei miei suggerimenti di Effective Java era che si introducesse una mappa statica all'interno dell'enum per gestire la ricerca inversa. Porting questo verso Kotlin con una semplice enum mi porta a codice che assomiglia a questo:Efficace Enum in Kotlin con ricerca inversa?

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

La mia domanda è, è questo il modo migliore per farlo, o c'è un modo migliore? Cosa succede se ho diverse enumerazioni che seguono uno schema simile? C'è un modo in Kotlin per rendere questo codice più riutilizzabile attraverso le enumerazioni?

risposta

50

Prima di tutto, l'argomento di fromInt() dovrebbe essere un Int, non un Int ?. Provare a ottenere un tipo usando null porterà ovviamente a null, e un chiamante non dovrebbe nemmeno provare a farlo. Anche la Mappa non ha motivo di essere mutabile. Il codice può essere ridotto a

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

Quel codice è così breve che, francamente, non sono sicuro che vale la pena provare a trovare una soluzione riutilizzabile.

+0

Stavo per raccomandare lo stesso. Inoltre, vorrei rendere 'FromInt' return non-null come' Enum.valueOf (String) ':' map [type]?: Throw IllegalArgumentException() ' – mfulton26

+2

Dato il supporto kotlin per null-safety, restituendo null dal metodo non mi infastidirebbe come in Java: il compilatore dovrà forzare il chiamante a gestire un valore restituito nullo e decidere cosa fare (lanciare o fare qualcos'altro). –

+0

Buon punto. Grazie. – mfulton26

5

Mi sono ritrovato a fare la ricerca inversa da codice personalizzato, codificato a mano, valore un paio di volte e sono arrivato con il seguente approccio.

Fai enum s implementano un'interfaccia comune:

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 
} 

Questa interfaccia (per quanto strano il nome è :)) segna un certo valore come il codice esplicito. L'obiettivo è quello di essere in grado di scrivere:

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

che può essere facilmente raggiunto con il seguente codice:

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

Questo è un sacco di lavoro per un'operazione così semplice, la risposta accettata è molto più pulita IMO –

+1

Completamente d'accordo per l'uso semplice è decisamente meglio. Ho avuto il codice sopra già per gestire nomi espliciti per il membro enumerato dato. – miensol

+0

Sono stupito di come sia arrivata questa soluzione. Penso che questo dovrebbe essere nella libreria stander. o qualcosa come guava o commons Apache ma per kotlin. – humazed

10

Non fa molto senso in questo caso, ma qui è un "estrazione logica" per @ di JBNized soluzione:

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 generale, questa è la cosa sugli oggetti compagno che possono essere riutilizzati (a differenza di membri statici in una classe Java)

-2

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

:)

2

possiamo usare find che restituisce il primo elemento corrispondente dato predicato, o null se non viene trovata tale elemento.

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