Ters aramayla Kotlin'de Etkili Numaralar?


105

Kotlin'deki bir enum üzerinde 'ters arama' yapmanın en iyi yolunu bulmaya çalışıyorum. Etkili Java'dan çıkarımlardan biri, geriye doğru aramayı işlemek için numaralandırma içine statik bir harita eklemenizdi. Bunu basit bir numaralandırma ile Kotlin'e taşımak beni şuna benzeyen bir koda götürüyor:

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]
        }
    }
}

Sorum şu, bunu yapmanın en iyi yolu bu mu yoksa daha iyi bir yol var mı? Benzer bir modeli izleyen birkaç numaram varsa ne olur? Kotlin'de bu kodu numaralandırmalar arasında daha kullanılabilir hale getirmenin bir yolu var mı?


Enum'unuz id özelliği ile Tanımlanabilir arabirim uygulamalı ve tamamlayıcı nesne, idToEnumValue haritasını tutan ve id'ye dayalı enum değerini döndüren soyut GettableById sınıfını genişletmelidir. Cevabımda detaylar aşağıda.
Eldar Agalarov

Yanıtlar:


178

Her şeyden önce argümanı bir fromInt()olmalıdır Int, bir değil Int?. TypeBoş değer almaya çalışmak , açıkça boşluğa yol açacaktır ve arayan kişi bunu yapmayı denememelidir bile. MapAyrıca değişken olması sebebi yok. Kod şu şekilde azaltılabilir:

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

Bu kod o kadar kısa ki, açıkçası, yeniden kullanılabilir bir çözüm bulmaya değip değmeyeceğinden emin değilim.


8
Ben de aynısını tavsiye edecektim. Ek olarak, aşağıdaki fromIntgibi boş olmayan bir dönüş yapardım Enum.valueOf(String):map[type] ?: throw IllegalArgumentException()
mfulton26

4
Boş güvenlik için kotlin desteği göz önüne alındığında, yöntemden null döndürmek beni Java'da olduğu gibi rahatsız etmeyecektir: arayan, derleyici tarafından boş döndürülen bir değerle uğraşmaya ve ne yapacağına (atma veya yapma başka bir şey).
JB Nizet

1
@Raphael çünkü numaralandırmalar Java 5'te ve İsteğe Bağlı Java 8'de tanıtıldı.
JB Nizet

2
bu kodun sürümünde daha güvenli erişim by lazy{}için mapve getOrDefault()tarafındanvalue
Hoang Tran

2
Bu çözüm iyi çalışıyor. Type.fromInt()Java kodundan arama yapabilmek için yönteme ile açıklama eklemeniz gerekeceğini unutmayın @JvmStatic.
Arto Bendiken

35

Kullanabileceğimiz findhangi böyle eleman bulunduğu takdirde ilk verilen yüklemi eşleşen elemanı veya null döndürür.

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

4
Bunun first { ... }yerine bariz bir geliştirme kullanılıyor çünkü birden fazla sonucun kullanımı yok.
creativecreatorormaybenot

9
Hayır, firstkullanımı, davranışı değiştirdiği için bir geliştirme değildir NoSuchElementExceptionve öğe, döndürmeye findeşit olan yerde bulunmazsa fırlatır . yani boş kullanım geri dönmek yerine atmak istiyorsanızfirstOrNullnullfirst
nemli

Bu yöntem birden çok değere sahip numaralandırmalarla kullanılabilir: fun valueFrom(valueA: Int, valueB: String): EnumType? = values().find { it.valueA == valueA && it.valueB == valueB } Ayrıca değerler fun valueFrom( ... ) = values().find { ... } ?: throw Exception("any message")var enumValue = EnumType.valueFrom(valueA, valueB) ?: throw Exception( ...)
enum'da

Yönteminizin doğrusal karmaşıklığı O (n) var. O (1) karmaşıklığına sahip önceden tanımlanmış HashMap'te aramayı kullanmak daha iyidir.
Eldar Agalarov

evet, biliyorum ama çoğu durumda, sıralama çok az sayıda duruma sahip olacaktır, bu yüzden her iki şekilde de önemli değil, hangisinin daha okunabilir olduğu.
humazed

27

Bu durumda pek mantıklı değil, ancak burada @ JBNized'in çözümü için bir "mantık çıkarma":

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

Genel olarak, refakatçi nesnelerin yeniden kullanılabilecekleri şey budur (Java sınıfındaki statik üyelerin aksine)


Neden açık sınıf kullanıyorsunuz? Sadece soyut yapın.
Eldar Agalarov

21

Daha "deyimsel" olarak kabul edilebilecek başka bir seçenek şudur:

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

Hangi daha sonra kullanılabilir Type[type].


Kesinlikle daha deyimsel! Şerefe.
AleksandrH

6

Kendimi özel, elle kodlanmış, birkaç kez değerle ters arama yaparken buldum ve aşağıdaki yaklaşımı buldum.

Make enums paylaşılan arabirim uygulamak:

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
}

Bu arayüz (adı ne kadar garip olursa olsun :)) belirli bir değeri açık kod olarak işaretler. Amaç şunları yazabilmektir:

val a = Alphabet::class.decode(1) //Alphabet.A
val d = Alphabet::class.tryDecode(4) //null

Aşağıdaki kodla kolayca elde edilebilir:

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
Bu kadar basit bir operasyon için çok fazla iş var, kabul edilen cevap çok daha temiz IMO
Connor Wyatt

2
Basit kullanım için tamamen katılıyorum, kesinlikle daha iyi. Yukarıdaki kod, verilen numaralandırılmış üye için açık isimleri işlemek için zaten vardı.
miensol

Kodunuz yansıma kullanıyor (kötü) ve şişirilmiş (çok kötü).
Eldar Agalarov

1

Sıralı alanı ve getValue kullanan önceki bazı tekliflerin bir varyantı aşağıdaki olabilir:

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

Başka bir örnek uygulama. Bu ayrıca, OPENhiçbir giriş hiçbir sıralama seçeneğiyle eşleşmiyorsa varsayılan değeri (burada ) ayarlar :

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

Daha genel bir çözümle geldi

inline fun <reified T : Enum<*>> findEnumConstantFromProperty(predicate: (T) -> Boolean): T? =
T::class.java.enumConstants?.find(predicate)

Örnek kullanım:

findEnumConstantFromProperty<Type> { it.value == 1 } // Equals Type.A

0

Gerçek Deyimsel Kotlin Yolu. Şişirilmiş yansıma kodu olmadan:

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])
}

0

Null check and invoke işleviyle kabul edilen çözümün biraz genişletilmiş yaklaşımı

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")
    }
}

-1

val t = Tür.değerler () [sıra]

:)


Bu 0, 1, ..., N sabitleri için işe yarar. Eğer 100, 50, 35 gibilerine sahipseniz, o zaman doğru bir sonuç vermeyecektir.
CoolMind
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.