如何在Kotlin中从Int创建枚举?

83

我有这个enum枚举类型:

enum class Types(val value: Int) {
    FOO(1)
    BAR(2)
    FOO_BAR(3)
}

我如何使用 Int 创建该 enum 的实例?

我尝试过类似以下的方式:

val type = Types.valueOf(1)

我遇到了这个错误:

整数文字不符合预期的字符串类型


1
Types.values().getOrNull(Int) - Vencat
12个回答

105
enum class Types(val value: Int) {
    FOO(1),
    BAR(2),
    FOO_BAR(3);

    companion object {
        fun fromInt(value: Int) = Types.values().first { it.value == value }
    }
}

你可能希望添加一个范围的安全检查并返回 null。


1
枚举的值是否已被弃用?这个代码对我有用,但是我必须将"value"更改为"ordinal"。 - AlanKley
3
it.value应该是指构造函数中定义的变量名。在这种情况下,它是val value: Int。而ordinal是完全不同的东西,它是枚举在列表中的位置。参见thisit.ordinal并不等同于引用自定义值,这正是代码中所做的。如果你给一个实例输入2,你将得到FOO_BAR,因为它在values[2]处。FOO_BAR的ordinal是2,但value是3。 - Zoe stands with Ukraine
fun fromInt(value: Int) = values().firstOrNull { it.value == value } ?: throw IllegalArgumentException("Invalid type: $value") - Kartik

61

Enum#valueOf 是基于名称的。这意味着要使用它,您需要使用 valueof("FOO")。因此,valueof 方法接受一个字符串参数,这解释了错误的原因。字符串不是 int 类型,类型很重要,这也是我提到它的原因,以便您知道这不是您要寻找的方法。

如果您想根据 int 值获取枚举值,则需要定义自己的函数来执行此操作。您可以使用 values() 获取 enum 的值,在这种情况下返回一个 Array<Types>。您可以使用 firstOrNull 作为安全方法,或者如果您更喜欢 null 而不是异常,则可以使用 first

因此,您需要添加一个 companion object(相对于枚举而言是静态的),因此您可以调用 Types.getByValue(1234)(从 Java 中调用 Types.COMPANION.getByValue(1234))而不是 Types.FOO.getByValue(1234)

companion object {
    private val VALUES = values()
    fun getByValue(value: Int) = VALUES.firstOrNull { it.value == value }
}

values()每次被调用都会返回一个新的数组,这意味着您应该将其本地缓存以避免每次调用getByValue时重新创建。如果在调用方法时调用values(),则有可能重复创建它(取决于实际调用次数),这将浪费内存。

诚然,正如评论中所讨论的那样,这可能是微不足道的优化,具体取决于您的使用情况。这意味着您也可以执行以下操作:

companion object {
    fun getByValue(value: Int) = values().firstOrNull { it.value == value }
}

如果这是出于可读性或其他原因而需要的话。

如果您想要进行扩展并根据多个参数进行检查,该函数也可以进行扩展。这些类型的函数不仅限于一个参数。


2
在 Kotlin 中,一切都被设计成不可变的,所以每个东西都是副本的副本的副本。你的代码中到处都有像“let”、“filter”、“map”这样的指令,而每一个指令都会留下未使用的对象。 对枚举值进行缓存听起来像微观优化。 - Anthony Chatellier
1
@AnthonyChatellier,从技术上讲,这是不正确的,因为它基于Java的工作原理。对于Kotlin Native可能不适用,但Native不在范围内。请参见https://dev59.com/31wY5IYBdhLWcg3wRGBS#32799324 - 缓存可能对方法使用频率有很大影响,但如果使用频率足够高,则会产生相当大的影响。您可以选择不包括缓存。 - Zoe stands with Ukraine
1
我不知道在技术上哪里有问题,但我同意你的观点,缓存可能对方法的使用频率产生很小的影响。例如,在我的笔记本电脑上,在20秒内调用getByValue 6,000,000,000次甚至没有消耗1%的CPU进行垃圾回收。因此,基于这个结果,即使只是一行代码,我也不确定它是否值得,对我来说这听起来像微小的优化。 - Anthony Chatellier
2
@ fvalasiad 不是的,因为您混淆了值和序数。 如果您添加 BAR_BAZ(4738653) 并查找 VALUES[4738653],那么会报错,因为 VALUES 中只有四个项。 您的解决方案应该是 fun getByIndex(index:Int)= VALUES.getOrNull(index),而不是按值获取。 如果您想按索引获取,那就没问题,但如果您想按值获取(这是问题所要求的),则不行。 - Zoe stands with Ukraine
1
...好吧,由于大小检查,它将返回null,但无论如何都不会返回任何项。 - Zoe stands with Ukraine
显示剩余2条评论

32

如果您只使用整数值来维护顺序,并且需要访问正确的值,则不需要任何额外的代码。您可以使用内置值ordinal。序数表示枚举声明中值的位置。

以下是一个示例:

enum class Types {
    FOO,               //Types.FOO.ordinal == 0 also position == 0
    BAR,               //Types.BAR.ordinal == 1 also position == 1
    FOO_BAR            //Types.FOO_BAR.ordinal == 2 also position == 2
}

您可以通过简单调用来访问序数值:

Types.FOO.ordinal
获取枚举正确的值,您可以直接调用:

Types.values()[0]      //Returns FOO
Types.values()[1]      //Returns BAR
Types.values()[2]      //Returns FOO_BAR

Types.values()返回枚举值,其顺序与声明的顺序相对应。

Types.values(Types.FOO.ordinal) == Types.FOO //This is true

如果整数值与顺序不匹配 (int_value != enum.ordinal),或者使用了不同的类型 (如字符串,浮点数...),那么您需要迭代并将自定义值进行比较,就像在此线程中已经提到的那样。


3
即使你是正确的,我也建议不要这样做。开发人员可能会更改项目的顺序或在其中添加新项目,并创建一个可能难以注意到的错误。但开发人员永远不会像其他答案中建议的那样更改enum中的int值,因此该错误永远不会发生。 - Igor Rodriguez

13

这取决于您实际想要做什么。

  • 如果您需要特定的硬编码枚举值,则可以直接使用 Types.FOO
  • 如果您从代码中的其他位置动态接收值,则应尝试直接使用枚举类型,以避免执行此类转换
  • 如果您从webservice接收值,则应该在您的反序列化工具中有一些方法来允许这种类型的转换(比如Jackson的@JsonValue
  • 如果您想基于枚举的某个属性(例如此处的value属性)获取枚举值,则恐怕您将不得不实现自己的转换方法,就像@Zoe所指出的那样。

实现此自定义转换的一种方法是添加一个带有转换方法的伴生对象:

enum class Types(val value: Int) {
    FOO(1),
    BAR(2),
    FOO_BAR(3);

    companion object {
        private val types = values().associate { it.value to it }

        fun findByValue(value: Int): Types? = types[value]
    }
}

Kotlin中的伴生对象旨在包含属于类但不与任何实例绑定(例如Java的static成员)的成员。 通过实现该方法,您可以通过调用以下方式访问该值:

var bar = Types.findByValue(2) ?: error("No Types enum value found for 2")

注意返回的值是可空的,以考虑参数没有对应枚举值的可能性。您可以使用Elvis运算符?:处理该情况,提供错误或默认值。


建议: fun findByValue(value: Int?) = types [value],以便在给定空值时返回null。 - Paulo Merson
1
我认为那不是处理空输入的正确位置。我从未需要过这样的东西。在 Kotlin 中,处理 Null 值非常容易,可能有一个更早的位置可以处理 Null 值。但当然这是情境相关的。 - Joffrey
确实是这样的情况。在我的情况下,这种乐趣经常用于将实体属性(枚举类型)映射到REST API中使用的JSON文档中的相应属性或数据库中的相应属性/列。许多时候,这些JSON和DB属性可以为null。 - Paulo Merson

6
如果您厌烦为每个枚举类型声明一个伴生对象 companion object{ ... } 以实现 EMotorcycleType.fromInt(...),这里有一个解决方案。
EnumCaster 对象:
object EnumCaster {
    inline fun <reified E : Enum<E>> fromInt(value: Int): E {
        return enumValues<E>().first { it.toString().toInt() == value }
    }
}

枚举示例:

枚举示例:

enum class EMotorcycleType(val value: Int){
    Unknown(0),
    Sport(1),
    SportTouring(2),
    Touring(3),
    Naked(4),
    Enduro(5),
    SuperMoto(6),
    Chopper(7),
    CafeRacer(8),

    .....
    Count(9999);

    override fun toString(): String = value.toString()
}

用例1:Kotlin枚举类型在JNI和应用程序中之间的相互转换

fun getType(): EMotorcycleType = EnumCaster.fromInt(nGetType())
private external fun nGetType(): Int

fun setType(type: EMotorcycleType) = nSetType(type.value)
private external fun nSetType(value: Int)

---- or ----

var type : EMotorcycleType
    get() = EnumCaster.fromInt(nGetType())
    set(value) = nSetType(value.value)

private external fun nGetType(): Int
private external fun nSetType(value: Int)

用法示例2:赋值给val

val type = EnumCaster.fromInt<EMotorcycleType>(aValidTypeIntValue)

val typeTwo : EMotorcycleType = EnumCaster.fromInt(anotherValidTypeIntValue)

2
这在多个层面上都是不安全的。我真的不建议这样做。在 EnumCaster.fromInt 中,没有关于枚举 E 的类型信息,这使您无法知道 toString() 是使用此黑客技巧实现的。为了更安全地实现这个想法,您应该查看姬方的答案 https://dev59.com/4VQJ5IYBdhLWcg3wl29a#71578372 - Joffrey

6
一种简单的方法可以是:
enum class Types(val value: Int) {
    FOO(1),
    BAR(2),
    FOO_BAR(3);

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

然后您可以使用


var bar = Types.valueOf(2)

如何在int未定义时设置值...例如,我们捕获或获取null?我想避免这种情况。 - Bart Mensfort
你可以做和 .valueOf() 一样的事情。加上 ?: throw IllegalArgumentException() - Konstantinos Bonis

4

以协议为导向的方式,具有类型安全性

interface RawRepresentable<T> {
    val rawValue: T
}

inline fun <reified E, T> valueOf(value: T): E? where E : Enum<E>, E: RawRepresentable<T> {
    return enumValues<E>().firstOrNull { it.rawValue == value }
}

enum class Types(override val rawValue: Int): RawRepresentable<Int> {
    FOO(1),
    BAR(2),
    FOO_BAR(3);
}

使用方法

val type = valueOf<Type>(2) // BAR(2)

您可以在非整数类型上使用它。


0

另一个选项...

enum class Types(val code: Int) {
    FOO(1),
    BAR(2),
    FOO_BAR(3);

    companion object {
        val map = values().associate { it.code to it }

        // Get Type by code with check existing codes and default
        fun getByCode(code: Int, typeDefault_param: Types = FOO): Types {
            return map[code] ?: typeDefault_param
        }
    }
}

fun main() {
    println("get 3: ${Types.getByCode(3)}")
    println("get 10: ${Types.getByCode(10)}")
}

获取3:FOO_BAR
获取10:FOO


0

这是为了帮助任何想要从其序数索引整数获取枚举的人。

enum class MyEnum { RED, GREEN, BLUE }
MyEnum.values()[1] // GREEN

另一种解决方案及其变体:

inline fun <reified T : Enum<T>> enumFromIndex(i: Int) = enumValues<T>()[i]
enumFromIndex<MyEnum>(1) // GREEN

inline fun <reified T : Enum<T>> enumFromIndex(i: Int) = enumValues<T>().getOrNull(i)
enumFromIndex<MyEnum>(3) ?: MyEnum.RED // RED

inline fun <reified T : Enum<T>> enumFromIndex(i: Int, default: T) =
    enumValues<T>().getOrElse(i) { default }
enumFromIndex(2, MyEnum.RED) // BLUE

这是另一个答案的改编版本。同时,感谢Miha_x64提供此答案


0

我会提前构建“反向”映射。可能不会有太大的改进,但代码也不多。

enum class Test(val value: Int) {
    A(1),
    B(2);

    companion object {
        val reverseValues: Map<Int, Test> = values().associate { it.value to it }
        fun valueFrom(i: Int): Test = reverseValues[i]!!
    }
}

编辑:map...toMap()已根据@hotkey的建议更改为associate

1
小建议:将 .map { ... }.toMap() 替换为 .associate { ... } - hotkey
@hotkey 很棒! - axblount
更短的代码:用 .associateBy { it.value } 替换 .associate { it.value to it } - Florian Moser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接