Kotlin中枚举类的完整指南

7,805 阅读9分钟

在编程时,一个有用的功能是能够表明一个变量只有有限的可能值。为了达到这个目的,大多数编程语言都引入了枚举的概念。

虽然枚举通常只是代表一个预定义常量值的命名列表,但Kotlin的枚举远不止于此。事实上,它们是真正的类,而不是简单的类型或有限的数据结构化。

这就转化为它们可以拥有自定义的属性和方法,实现接口,使用匿名类,以及更多。因此,Kotlin的枚举类在语言中起到了至关重要的作用。

另外,采用枚举使你的代码更易读,更少出错。这就是为什么每个Kotlin开发者都应该知道如何使用它们。所以,让我们深入了解枚举类,看看你需要学习的一切,以掌握它们。

Kotlin枚举类与Java枚举类型的对比

Java中,枚举是一种类型。具体来说,官方文档将枚举类型定义为 "一种特殊的数据类型,可以使一个变量成为一组预定义的常量。" 这意味着上述变量必须等于其中一个预定义值。这些值是常数,代表枚举类型的属性。

尽管是一个类型,Javaenum 声明实际上在幕后创建了一个类。因此,Java枚举可以包括自定义方法和属性。这一点,除了由编译器自动添加的默认方法和属性之外。就是这样--对于Java枚举类型,没有什么可以做的了。

与Java不同的是,Kotlin枚举是原生的类,而不仅仅是在幕后。这就是为什么它们被称为枚举类,而不是Java枚举类型。这可以防止开发者把它们看作仅仅是常量的集合,就像在Java中可能发生的那样。

正如我们即将看到的,Kotlin枚举的作用远不止于此。它们不仅可以使用匿名类,还可以实现接口,就像其他Kotlin类一样。所以,让我们忘记Java枚举类型,开始深入研究Kotlin枚举类。

Kotlin枚举的基本特征

让我们开始探索Kotlin枚举所提供的最常见的功能。

定义枚举

Kotlin枚举类最基本的使用情况是把它们当作常量的集合。在这种情况下,它们被称为类型安全的枚举,可以被定义如下。

enum class Day {   
    MONDAY, 
    TUESDAY,
    WEDNESDAY, 
    THURSDAY, 
    FRIDAY, 
    SATURDAY,
    SUNDAY
}

正如你所看到的,enum 关键字之后是class 关键字。这应该可以防止你被愚弄,以为Kotlin枚举仅仅是类型。

然后是枚举类的名称。最后,在枚举类的体内,可能的逗号分隔的选项被称为枚举常量。注意,由于它们是常量,它们的名字应该总是用大写字母。这就是最简单的Kotlin枚举类的组成。

初始化枚举

Kotlin枚举是类,这意味着它们可以有一个或多个构造函数。因此,你可以通过向一个有效的构造函数传递所需的值来初始化枚举常量。这是可能的,因为枚举常量无非是枚举类本身的实例。
让我们通过一个例子来看看这是如何工作的。

enum class Day(val dayOfWeek: Int) {    
    MONDAY(1), 
    TUESDAY(2),
    WEDNESDAY(3), 
    THURSDAY(4), 
    FRIDAY(5), 
    SATURDAY(6),
    SUNDAY(7)
}

这样一来,每个枚举常量都与一周中的相对数字相关联。

通常,基于构造函数的方法被用来为枚举常量提供有用的信息或有意义的值。最常见的情况之一是为它们提供一个自定义的printableName 属性。这在打印它们时非常有用,可以通过以下方式实现。

enum class Day(val printableName: String) {    
    MONDAY("Monday"), 
    TUESDAY("Tuesday"),
    WEDNESDAY("Wednesday"), 
    THURSDAY("Thursday"), 
    FRIDAY("Friday"), 
    SATURDAY("Saturday"),
    SUNDAY("Sunday")
}

内建的属性

Kotlin枚举类有一些内置的属性。就像在Java中发生的那样,它们被编译器自动添加到每个枚举类中。所以,你可以在任何枚举类实例中访问它们。让我们来看看它们。

  1. ordinal
    ordinal 允许你检索当前枚举常量在列表中出现的位置。它是一个基于零的索引,这意味着选项列表中第一个常量的值是0 ,第二个是1 ,以此类推。在实现Comparable 接口时,这个属性将被用于排序逻辑。
  2. name
    name 以字符串形式返回枚举常量的名称。

让我们通过下面的例子来看看这两者的作用。

enum class Day(val dayOfWeek: Int) {
    MONDAY(1), 
    TUESDAY(2),
    WEDNESDAY(3), 
    THURSDAY(4), 
    FRIDAY(5), 
    SATURDAY(6),
    SUNDAY(7)
}

fun main() {    
    for (day in DAY.values())
        println("[${day.ordinal}] -> ${day.name} (${day.dayOfWeek}^ day of the week)")
}

通过运行这段代码,你会得到以下结果。

[0] -> MONDAY (1^ day of the week)
[1] -> TUESDAY (2^ day of the week)
[2] -> WEDNESDAY (3^ day of the week)
[3] -> THURSDAY (4^ day of the week)
[4] -> FRIDAY (5^ day of the week)
[5] -> SATURDAY (6^ day of the week)
[6] -> SUNDAY (7^ day of the week)

正如你所看到的,由name 内建属性返回的字符串与常量本身相吻合。这并不能使它们具有很强的可打印性,这就是为什么添加一个自定义的printableName 属性可能是有用的。

另外,这个例子还强调了为什么也需要添加一个自定义的索引。这是因为ordinal 是一个基于零的索引,旨在编程时使用,而不是提供信息内容。

Kotlin枚举的高级功能

现在,是时候深入研究Kotlin枚举类所提供的最先进和最复杂的功能了。

添加自定义属性和方法

自定义属性和方法可以被添加到枚举类中,就像在任何其他Kotlin类中一样。改变的是语法,它必须遵循特定的规则。

特别是,方法和属性必须被添加到枚举常量定义的下面,在分号之后。就像Kotlin中的任何其他属性一样,你可以为它们提供一个默认值。另外,枚举类可以有实例和静态方法。

enum class Day {
    MONDAY(1, "Monday"),
    TUESDAY(2, "Tuesday"),
    WEDNESDAY(3, "Wednesday"),
    THURSDAY(4, "Thursday"),
    FRIDAY(5, "Friday"),
    SATURDAY(6, "Saturday"),
    SUNDAY(7, "Sunday"); // end of the constants

    // custom properties with default values
    var dayOfWeek: Int? = null
    var printableName : String? = null

    constructor()

    // custom constructors
    constructor(
        dayOfWeek: Int,
        printableName: String
    ) {
        this.dayOfWeek = dayOfWeek
        this.printableName = printableName
    }

    // custom method
    fun customToString(): String {
        return "[${dayOfWeek}] -> $printableName"
    }
}

在这个例子中,一个自定义构造函数、两个自定义属性和一个自定义实例方法被添加到枚举类中。属性和方法可以通过实例访问,也就是枚举常量,语法如下。

// accessing the dayOfWeek property
Day.MONDAY.dayOfWeek

// accessing the customToString() method
Day.MONDAY.customToString()

请记住,Kotlin没有静态方法的概念。然而,通过利用Kotlin枚举类所支持的同伴对象,也可以达到同样的效果。在同伴对象中定义的方法不依赖于特定的实例,可以静态地访问。你可以添加一个,如下所示。

enum class Day {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY;

    companion object {
        fun getNumberOfDays() = values().size
    }
}

现在,你可以通过这种方式访问getNumberOfDays() 方法。

Day.getNumberOfDays()

正如你所看到的,该方法在类上被静态地调用,不依赖于任何实例。请注意,在实现它时,使用了合成静态方法values() 。你很快就会看到它是什么以及如何使用它。

使用匿名类来定义枚举常量

我们可以创建匿名类来定义特定的枚举常量。与Java相比,Kotlin的枚举类支持匿名类。

特别是,枚举常量可以被匿名类实例化。他们只需要对枚举类本身的抽象方法给出一个实现。这可以通过下面的语法来实现。

enum class Day {
    MONDAY {
        override fun nextDay() = TUESDAY
    },
    TUESDAY {
        override fun nextDay() = WEDNESDAY
    },
    WEDNESDAY {
        override fun nextDay() = THURSDAY
    },
    THURSDAY {
        override fun nextDay() = FRIDAY
    },
    FRIDAY {
        override fun nextDay() = SATURDAY
    },
    SATURDAY {
        override fun nextDay() = SUNDAY
    },
    SUNDAY {
        override fun nextDay() = MONDAY
    };

    abstract fun nextDay(): Day
}

如图所示,每个枚举常量都是通过声明自己的匿名类来实例化的,同时覆盖所需的抽象方法。这就像在任何其他Kotlin匿名类中发生的那样。

枚举可以实现接口

尽管Kotlin枚举类不能派生自类、枚举类或抽象类,但它们实际上可以实现一个或多个接口。

在这种情况下,每个枚举常量必须提供一个接口方法的实现。这可以通过一个共同的实现来实现,如下所示。

interface IDay {
    fun firstDay(): Day
}


enum class Day: IDay {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY;

    override fun firstDay(): Day {
      return MONDAY  
    } 
}

或者像之前显示的那样,通过使用匿名类。

interface IDay {
    fun nextDay(): Day
}


enum class Day: IDay {
    MONDAY {
        override fun nextDay() = TUESDAY
    },
    TUESDAY {
        override fun nextDay() = WEDNESDAY
    },
    WEDNESDAY {
        override fun nextDay() = THURSDAY
    },
    THURSDAY {
        override fun nextDay() = FRIDAY
    },
    FRIDAY {
        override fun nextDay() = SATURDAY
    },
    SATURDAY {
        override fun nextDay() = SUNDAY
    },
    SUNDAY {
        override fun nextDay() = MONDAY
    };
}

在这两种情况下,每个枚举常量都有IDay 接口方法的实现。

运行中的枚举

现在你已经看到了基本和高级的功能,你已经具备了开始使用Kotlin枚举类的一切条件。让我们通过三个最常见的用例来看看它们的运行情况。

枚举和when

枚举类在与Kotlin的 [when](https://kotlinlang.org/docs/control-flow.html#when-expression)条件性语句时特别有用。这是因为when 表达式必须考虑到每个可能的条件。换句话说,它们必须是详尽的。

由于枚举在定义上提供了一组有限的值,Kotlin可以利用这一点来计算出是否考虑了每个条件。如果没有,在编译时就会抛出一个错误。让我们通过when 表达式来看看枚举的作用。

enum class Day {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}

fun main (currentDay: Day) {
    when (currentDay) {
        Day.MONDAY -> work()
        Day.TUESDAY -> work()
        Day.WEDNESDAY -> work()
        Day.THURSDAY -> work()
        Day.FRIDAY -> work()
        Day.SATURDAY -> rest()
        Day.SUNDAY -> rest()
    }
}

fun work() {
    println("Working")
}

fun rest() {
    println("Resting")
}

如刚才所示,枚举允许你根据它们的值来区分逻辑。它们也使你的代码更易读,更不容易出错。这是因为它们确定了在一个when 语句中要考虑的可能选项的最大数量。这样一来,你就不会忘记一个。

枚举和Kotlin合成方法

与前面提到的内置属性类似,每个枚举类也有合成方法。它们由Kotlin在编译时自动添加,代表了可以静态访问的实用函数。让我们看看最重要的方法以及如何使用它们。

  • values()
    它返回枚举类中包含的所有枚举常量的列表。
  • valueOf(value: String)
    它返回enum常量,其name 属性与作为参数传递的值字符串相匹配。如果没有找到,会抛出一个 [IllegalArgumentException](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-illegal-argument-exception/)会被抛出。

让我们通过一个例子来看看它们的作用。

enum class Day(val printableName: String) {
    MONDAY("Monday"),
    TUESDAY("Tuesday"),
    WEDNESDAY("Wednesday"),
    THURSDAY("Thursday"),
    FRIDAY("Friday"),
    SATURDAY("Saturday"),
    SUNDAY("Sunday")
}

fun main () {
    for (day in Day.values())        
        println(day.printableName)

   println(Day.valueOf("MONDAY").printableName)
}

当运行时,将打印出以下结果。

Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
Monday

请注意,通过采用以下两个Kotlin全局函数可以得到同样的结果。 [enumValues<T>()](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/enum-values.html)enumValueOf<T>().它们允许你用基于通用的方法访问任何枚举类T

遍历枚举

最后,这两个用例可以结合起来,通过values() 合成方法对它们进行迭代,并根据它们的值用when 表达式执行不同的动作。让我们看看基于这种方法的一个例子。

enum class Day(val printableName: String) {
    MONDAY("Monday"),
    TUESDAY("Tuesday"),
    WEDNESDAY("Wednesday"),
    THURSDAY("Thursday"),
    FRIDAY("Friday"),
    SATURDAY("Saturday"),
    SUNDAY("Sunday")
}

fun main () {
    for (day in Day.values()) {
        // common behavior
        println(day.printableName)

        // action execute based on day value
        when (day) {
            Day.MONDAY -> work()
            Day.TUESDAY -> work()
            Day.WEDNESDAY -> work()
            Day.THURSDAY -> work()
            Day.FRIDAY -> work()
            Day.SATURDAY -> rest()
            Day.SUNDAY -> rest()
        }

        // common behavior
        println("---")
    }
}

fun work() {
    println("Working")
}

fun rest() {
    println("Resting")
}

这样,就可以根据枚举类所包含的每个当前可能的值来执行自定义逻辑。如果启动,该片段将返回这个。

Monday
Working
---
Tuesday
Working
---
Wednesday
Working
---
Thursday
Working
---
Friday
Working
---
Saturday
Resting
---
Sunday
Resting
---

结论

在这篇文章中,我们看了什么是Kotlin枚举类,何时和如何使用它们,以及为什么。如图所示,Kotlin枚举具有许多特性,为你提供了无限的可能性。因此,简单地把它们看作是一组常量是错误的,这与许多其他编程语言中的情况不同。

由于Kotlin枚举是类,它们可以有自己的属性、方法,并实现接口。另外,如果使用得当,它们可以使你的代码更清晰、更易读、更少出错。这就是为什么每个Kotlin开发者都应该使用它们,而教授正确使用它们所需的一切就是本文的目的。

谢谢你的阅读!我希望你觉得这篇文章对你有帮助。如果有任何问题、评论或建议,请随时与我联系。

The postA complete guide to enum classes in Kotlinappeared first onLogRocket Blog.