article banner (priority)

Enum classes in Kotlin

In this chapter, we're going to introduce the concept of enum classes. Let's start with an example. Suppose that you’re implementing a payment method that has to support three possible options: cash payment, card payment, and bank transfer. The most basic way to represent a fixed set of values in Kotlin is an enum class. Inside its body, we list all the values, divided by a comma. We name values using UPPER_SNAKE_CASE notation (e.g., BANK_TRANSFER). Enum class elements can be referenced by the enum name, followed by a dot, and then the value name (e.g., PaymentOption.CASH). All values are typed as the enum class type.

enum class PaymentOption { CASH, CARD, TRANSFER, } fun printOption(option: PaymentOption) { println(option) } fun main() { val option: PaymentOption = PaymentOption.CARD println(option) // CARD printOption(option) // CARD }

Each enum class has the following companion object elements:

  • entries property, which keeps a list of all the values of this enum class. It is a modern replacement of the values function, that returns an array of elements3.
  • valueOf function, which parses a string into a value matching its name (this is case-sensitive) or throws an exception.
enum class PaymentOption { CASH, CARD, TRANSFER, } fun main() { val option: PaymentOption = PaymentOption.valueOf("TRANSFER") println(option) println("All options: ") val paymentOptions: List<PaymentOption> = PaymentOption.entries for (paymentOption in paymentOptions) { println(paymentOption) } } // TRANSFER // All options: // CASH // CARD // TRANSFER

Instead of these methods, we can also use the top-level enumValues and enumValueOf functions.

enum class PaymentOption { CASH, CARD, TRANSFER, } fun main() { val option = enumValueOf<PaymentOption>("TRANSFER") println(option) println("All options: ") val paymentOptions = enumValues<PaymentOption>() for (paymentOption in paymentOptions) { println(paymentOption) } } // TRANSFER // All options: // CASH // CARD // TRANSFER

As you can see, enum elements keep their values in order. This order is important. Each enum value has two properties:

  • name - the name of this value,
  • ordinal - the position of this value (starting from 0).
enum class PaymentOption { CASH, CARD, TRANSFER, } fun main() { val option = PaymentOption.TRANSFER println(option.name) // TRANSFER println(option.ordinal) // 2 }

Each enum class is a subclass of the abstract class Enum. This superclass guarantees the name and ordinal properties. Enum classes have properties that implement toString, equals, and hashCode, but, unlike data classes, they also have compareTo (their natural order is the order of the elements in the body).

Enum values can be used inside when-conditions. Moreover, there is no need to use the else-branch when all possible enum values are covered.

fun transactionFee(paymentOption: PaymentOption): Double = when (paymentOption) { PaymentOption.CASH -> 0.0 PaymentOption.CARD, PaymentOption.TRANSFER -> 0.05 }

Enum classes are very convenient because they can be easily parsed or stringified. They are a popular way to represent a finite set of possible values.

Data in enum values

In Kotlin, each enum value can hold a state. It is possible to define a primary constructor for an enum class, and then each value needs to specify its data next to its name. It is the best practice that enum values should always be immutable, so their state should never change.

import java.math.BigDecimal enum class PaymentOption(val commission: BigDecimal) { CASH(BigDecimal.ONE), CARD(BigDecimal.TEN), TRANSFER(BigDecimal.ZERO) } fun main() { println(PaymentOption.CARD.commission) // 10 println(PaymentOption.TRANSFER.commission) // 0 val paymentOption: PaymentOption = PaymentOption.entries.random() println(paymentOption.commission) // 0, 1 or 10 }

Enum classes with custom methods

Kotlin enums can have abstract methods whose implementations are item-specific. When we define them, the enum class itself needs to define an abstract method, and each item must override it:

enum class PaymentOption { CASH { override fun startPayment( transaction: Transaction ) { showCashPaymentInfo(transaction) } }, CARD { override fun startPayment( transaction: Transaction ) { moveToCardPaymentPage(transaction) } }, TRANSFER { override fun startPayment( transaction: Transaction ) { showMoneyTransferInfo() setupPaymentWatcher(transaction) } }; abstract fun startPayment(transaction: Transaction) }

This option is not popular as we generally prefer using functional primary constructor parameters1 or extension functions2.

Summary

Enum classes are a convenient way to represent a concrete set of possible values. Each value has the properties name and ordinal (position). We can get an array of all values using the values companion object function or the enumValues top-level function. We can also parse an enum value from String using the valueOf companion object function or the enumValueOf top-level function.

In the next chapter, we will talk about sealed classes, which are often treated as similar to enums but represent completely different and even more powerful abstractions. Sealed classes can form a closed hierarchy of classes, whereas enums represent only a set of constant values.

1:

Functional variables are described in the book Functional Kotlin. An example of using an enum class with functional primary constructor parameters is presented in Effective Kotlin, Item 41: Use enum to represent a list of values.

2:

Extension functions are described later in this book.

3:

entries property was introduced in Kotlin 1.9, so in projects using older versions of Kotlin you need to use values instead.