リバースルックアップを使用したKotlinの有効な列挙型?


102

Kotlinの列挙型で「逆ルックアップ」を行うための最良の方法を見つけようとしています。私が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にありますか?


EnumはidプロパティでIdentifiableインターフェースを実装し、コンパニオンオブジェクトはidToEnumValueマップを保持し、idに基づいて列挙値を返す抽象クラスGettableByIdを拡張する必要があります。詳細は以下の私の答えです。
Eldar Agalarov

回答:


175

まず第一に、の引数はでなくであるfromInt()必要がIntありInt?ます。Typenullを使用して取得しようとすると、明らかにnullにつながるため、呼び出し元はそれを試みるべきではありません。Mapまた、可変すべき理由はありません。コードは次のように削減できます。

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

そのコードは非常に短いので、正直なところ、再利用可能なソリューションを探すのに値するかどうかはわかりません。


8
同じことをすすめようとしていました。また、私はなるだろうfromIntのようなリターンnull以外Enum.valueOf(String)map[type] ?: throw IllegalArgumentException()
mfulton26

4
null-safetyのkotlinサポートを考えると、メソッドからnullを返すことは、Javaの場合のように私を悩ませることはありません。呼び出し側は、戻り値nullを処理するようコンパイラーに強制され、何をすべきか(スローまたは実行するか)を決定します他のもの)。
JBニゼット2016年

1
列挙型は、Java 8でJava 5およびオプションで導入された@Raphaelので
JB Nizet

2
このコードの私のバージョンはby lazy{}mapおよびgetOrDefault()より安全なアクセスのために使用value
Hoang Tran

2
このソリューションはうまく機能します。Type.fromInt()Javaコードから呼び出せるようにするには、メソッドにで注釈を付ける必要があります@JvmStatic
Arto Bendiken、

34

指定された述語に一致する最初の要素find返す which を使用できます。そのような要素が見つからなかった場合はnullを使用できます。

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

4
first { ... }複数の結果を使用する必要がないため、代わりにを使用していることは明らかです。
creativecreatoror'may

9
いいえ、使用するfirstことが行動を変化させ、スローとしてエンハンスメントないNoSuchElementException場合項目が見つからない場合findに等しいfirstOrNull戻りますnull。したがって、nullを返す代わりにスローしたい場合はfirst
4

この方法では、複数の値を持つ列挙型で使用することができます。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でルックアップを使用する方が良い。
Eldar Agalarov

はい、わかっていますが、ほとんどの場合、列挙型には非常に少数の状態があるため、どちらの方法でも問題ありません。
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

一般に、それらは再利用できるコンパニオンオブジェクトに関するものです(Javaクラスの静的メンバーとは異なります)。


オープンクラスを使用する理由 抽象化してください。
Eldar Agalarov

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
それはそのような単純な操作のための多くの仕事です、受け入れられた答えははるかにきれいですIMO
Connor Wyatt

2
シンプルな使い方には完全に同意します。与えられた列挙型メンバーの明示的な名前を処理するために、上記のコードはすでにありました。
miensol 2016

リフレクションを使用したコード(悪い)と肥大化した(悪い)。
Eldar Agalarov

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オプションに一致しない場合にもデフォルト値(ここでは)を設定します。

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

val t = Type.values()[ordinal]

:)


これは定数0、1、...、Nで機能します。100、50、35のような定数がある場合、正しい結果が得られません。
CoolMind 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.