【问题标题】:Effective Enums in Kotlin with reverse lookup?Kotlin 中具有反向查找的有效枚举?
【发布时间】:2016-10-14 04:09:03
【问题描述】:

我正在尝试找到对 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 接口,并且伴随对象应该扩展抽象类 GettableById,它包含 idToEnumValue 映射并基于 id 返回枚举值。详细信息在我的回答中。

标签: enums kotlin


【解决方案1】:

首先,fromInt() 的参数应该是Int,而不是Int?。尝试使用 null 获取Type 显然会导致 null,调用者甚至不应该尝试这样做。 Map 也没有理由是可变的。代码可以简化为:

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

那段代码太短了,坦率地说,我不确定是否值得尝试寻找可重用的解决方案。

【讨论】:

  • 我正要推荐同样的。另外,我会让fromIntEnum.valueOf(String) 一样返回非空值:map[type] ?: throw IllegalArgumentException()
  • 鉴于 kotlin 对 null 安全性的支持,从该方法返回 null 不会像在 Java 中那样困扰我:编译器将强制调用者处理 null 返回值,并且决定做什么(扔或做其他事情)。
  • @Raphael 因为枚举是在 Java 5 中引入的,而在 Java 8 中是可选的。
  • 此代码的我的版本使用by lazy{} 作为mapgetOrDefault(),以便value 更安全地访问
  • 这个解决方案效果很好。请注意,为了能够从 Java 代码中调用 Type.fromInt(),您需要使用 @JvmStatic 注释该方法。
【解决方案2】:

我们可以使用find,它返回匹配给定谓词的第一个元素,如果没有找到这样的元素,则返回null。

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

【讨论】:

  • 一个明显的增强是使用first { ... } 代替,因为没有使用多个结果。
  • 不,使用first 不是增强功能,因为它会改变行为并在找不到项目时抛出NoSuchElementException,而find 等于firstOrNull 返回null。所以如果你想抛出而不是返回 null 使用 first
  • 此方法可用于具有多个值的枚举:fun valueFrom(valueA: Int, valueB: String): EnumType? = values().find { it.valueA == valueA &amp;&amp; it.valueB == valueB } 如果值不在枚举中,您也可以抛出异常:fun valueFrom( ... ) = values().find { ... } ?: throw Exception("any message") 或者您可以在调用此方法时使用它: var enumValue = EnumType.valueFrom(valueA, valueB) ?: throw Exception( ...)
  • 您的方法具有线性复杂度 O(n)。最好在 O(1) 复杂度的预定义 HashMap 中使用查找。
  • 是的,我知道,但在大多数情况下,枚举的状态数非常少,所以无论哪种方式都无所谓,什么更具可读性。
【解决方案3】:

在这种情况下没有多大意义,但这里是@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 类中的静态成员不同)

【讨论】:

  • 你为什么使用公开课?把它抽象化。
【解决方案4】:

另一个可能被认为更“惯用”的选项如下:

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

然后可以像Type[type]一样使用。

【讨论】:

  • 绝对更地道!干杯。
【解决方案5】:

我发现自己通过自定义、手动编码、值几次进行反向查找,并想出了以下方法。

enums 实现一个共享接口:

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)

【讨论】:

  • 这么简单的操作需要做很多工作,接受的答案比 IMO 干净得多
  • 完全同意简单使用它肯定更好。我已经有了上面的代码来处理给定枚举成员的显式名称。
  • 您的代码使用反射(不好)并且臃肿(也不好)。
【解决方案6】:

另一个示例实现。如果没有输入匹配没有枚举选项,这也会设置默认值(此处为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
}

}

【讨论】:

  • 此解决方案表现良好,并提供了提供默认值或?: throw IllegalArgumentException(status.toString())的选项
【解决方案7】:

如果你有很多枚举,这可能会节省一些击键:

inline fun <reified T : Enum<T>, V> ((T) -> V).find(value: V): T? {
    return enumValues<T>().firstOrNull { this(it) == value }
}

像这样使用它:

enum class Algorithms(val string: String) {
    Sha1("SHA-1"),
    Sha256("SHA-256"),
}

fun main() = println(
    Algorithms::string.find("SHA-256")
            ?: throw IllegalArgumentException("Bad algorithm string: SHA-256")
)

这将打印Sha256

【讨论】:

    【解决方案8】:

    一些先前提议的变体可能如下,使用序数字段和 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)
        }
    }
    

    }

    【讨论】:

      【解决方案9】:

      真正惯用的 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])
      }
      

      【讨论】:

        【解决方案10】:

        想出了一个更通用的解决方案

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

        示例用法:

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

        【讨论】:

          【解决方案11】:

          具有空值检查和调用功能的公认解决方案的略微扩展方法

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

          【讨论】:

            【解决方案12】:

            根据您的示例,我可能会建议删除关联值并仅使用类似于索引的ordinal

            ordinal - 返回此枚举常量的序号(它在其枚举声明中的位置,其中初始常量的序号为零)。

            enum class NavInfoType {
                GreenBuoy,
                RedBuoy,
                OtherBeacon,
                Bridge,
                Unknown;
            
                companion object {
                    private val map = values().associateBy(NavInfoType::ordinal)
                    operator fun get(value: Int) = map[value] ?: Unknown
                }
            }
            

            如果map 返回null,我想返回Unknown。您还可以通过将 get 替换为以下内容来引发非法参数异常:

            operator fun get(value: Int) = map[value] ?: throw IllegalArgumentException()
            

            【讨论】:

              【解决方案13】:

              一种重用代码的方法:

              interface IndexedEnum {
                  val value: Int
              
                  companion object {
                      inline fun <reified T : IndexedEnum> valueOf(value: Int) =
                          T::class.java.takeIf { it.isEnum }?.enumConstants?.find { it.value == value }
                  }
              }
              

              然后枚举可以被索引:

              enum class Type(override val value: Int): IndexedEnum {
                  A(1),
                  B(2),
                  C(3)
              }
              

              并像这样反向搜索:

              IndexedEnum.valueOf<Type>(3)
              

              【讨论】:

                【解决方案14】:

                val t = Type.values()[序数]

                :)

                【讨论】:

                • 这适用于常量 0, 1, ..., N。如果您将它们设为 100、50、35,那么它不会给出正确的结果。
                猜你喜欢
                • 2014-07-17
                • 1970-01-01
                • 2011-07-16
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2023-03-03
                相关资源
                最近更新 更多