역방향 조회를 사용하는 Kotlin의 효과적인 열거 형?


103

Kotlin의 열거 형에서 '역방향 조회'를 수행하는 가장 좋은 방법을 찾으려고합니다. Effective Java에서 얻은 내용 중 하나는 역방향 조회를 처리하기 위해 열거 형 내부에 정적 맵을 도입했다는 것입니다. 간단한 열거 형을 사용하여 이것을 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에 따라 enum 값을 반환하는 추상 클래스 GettableById를 확장해야합니다. 자세한 내용은 내 대답에 있습니다.
— Eldar Agalarov

답변:


177

우선의 인수는 fromInt()이어야 Int합니다 Int?. Typeusing null 을 얻으려고 시도 하면 분명히 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 에서처럼 나를 괴롭히지 않을 것입니다. 다른 것).
— JB Nizet

1
열거 형은 자바 8에 자바 5와 옵션에 도입 @Raphael 때문에
— JB Nizet

2
이 코드 사용의 나의 버전 by lazy{}에 대한 map및 getOrDefault()에 의해 안전한 액세스value
— 호앙 트란

2
이 솔루션은 잘 작동합니다. Type.fromInt()Java 코드에서 호출 할 수 있으려면 메소드에 @JvmStatic.
— Arto Bendiken

35

우리가 사용할 수 find있는 , 이러한 요소가 발견 된 경우 먼저 주어진 조건에 일치하는 요소를 돌려줍니다.

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

4
first { ... }여러 결과에 대한 사용이 없기 때문에 명백한 향상이 대신 사용 됩니다.
— creativecreatorormaybenot

9
아니요, using first은 동작을 변경하고 returns와 동일한 NoSuchElementException항목을 찾을 수없는 경우 throw하므로 향상 되지 않습니다 . 그래서 만약 당신이 null 사용을 반환하는 대신 던지고 싶다면findfirstOrNullnullfirst
— humazed a

이 메서드는 여러 값이있는 열거 형과 함께 사용할 수 있습니다. fun valueFrom(valueA: Int, valueB: String): EnumType? = values().find { it.valueA == valueA && it.valueB == valueB } 또한 값이 열거 형에없는 경우 예외를 throw 할 수 있습니다. 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입니다
— 코너 와이어트

2
단순한 사용에 완전히 동의하는 것이 확실히 낫습니다. 주어진 열거 멤버에 대한 명시 적 이름을 처리하기 위해 위의 코드가 이미 있습니다.
— miensol

리플렉션을 사용하는 코드 (나쁨)와 부풀어 오름 (나쁨).
— 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 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

진정한 관용적 Kotlin 방식. 부풀어 오른 반사 코드없이 :

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 ​​() [서수]

:)


이것은 상수 0, 1, ..., N에 대해 작동합니다. 100, 50, 35와 같은 값이 있으면 올바른 결과를 제공하지 않습니다.
— CoolMind
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.