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
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()
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)
I found myself doing the reverse lookup by custom, hand coded, value couple of times and came of up with following approach.
Make enum
s 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)
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 }
}
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)
}
}
}
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")
}
}