역방향 조회를 사용하는 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{}에 대한 mapgetOrDefault()에 의해 안전한 액세스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.