私はKotlinのenumで「逆ルックアップ」を行う最良の方法を見つけようとしています。 Effective Javaから学んだことの1つは、逆ルックアップを処理するために列挙内に静的マップを導入することでした。これを単純な列挙型でKotlinに移植すると、次のようなコードになります。
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]
}
}
}
私の質問は、これがこれを行う最良の方法ですか、それともより良い方法がありますか?同様のパターンに従う複数の列挙型がある場合はどうなりますか? Kotlinには、このコードを列挙間で再利用可能にする方法がありますか?
まず、fromInt()
の引数はInt?
ではなくInt
である必要があります。 nullを使用してType
を取得しようとすると、明らかにnullにつながるため、呼び出し側はそれを試みてはいけません。 Map
には、変更する理由もありません。コードは次のように縮小できます。
companion object {
private val map = Type.values().associateBy(Type::value)
fun fromInt(type: Int) = map[type]
}
そのコードは非常に短いので、率直に言って、再利用可能なソリューションを見つけることを試みる価値があるかどうかはわかりません。
この場合はあまり意味がありませんが、@ 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
一般に、それは再利用できるコンパニオンオブジェクトに関するものです(Javaクラスの静的メンバーとは異なります)
find
whichを使用できます。指定された述語に一致する最初の要素を返します。そのような要素が見つからなかった場合はnullを返します。
companion object {
fun valueOf(value: Int): Type? = Type.values().find { it.value == value }
}
私は自分自身で、手作業でコード化された値を数回逆ルックアップしていることに気付き、次のアプローチを思いつきました。
enum
sに共有インターフェースを実装させます。
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)
より「イディオマティック」と考えられる別のオプションは次のとおりです。
companion object {
private val map = Type.values().associateBy(Type::value)
operator fun get(value: Int) = map[value]
}
Type[type]
のように使用できます。
別の実装例。また、入力が列挙オプションに一致しない場合、デフォルト値(ここでは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
}
}
いくつかの以前の提案のバリエーションは、序数フィールドと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)
}
}
}