## Kotlin Apprentice

Third Edition · Android 11 · Kotlin 1.4 · IntelliJ IDEA 2020.3

#### Before You Begin

Section 0: 4 chapters

#### Section II: Collections & Lambdas

Section 2: 3 chapters

#### Section III: Building Your Own Types

Section 3: 8 chapters

# 16. Enum & Sealed Classes Written by Ellen Shapiro

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Sometimes you’ll have a piece of information that could (or at least, should) have only one of a limited number of potential values. Using what you already know, you could make a `List` of all the acceptable values for that piece of information, and walk (or enumerate) through each value, one-by-one, to see if your new piece of information matches one of the expected values.

If you think that sounds boring and repetitive, you’re not alone. This is why the concept of the enum was invented.

Note: There is some debate over how to pronounce the word enum. Since it derives from “enumeration,” some people pronounce it ee-noom. Some people pronounce it ee-numb, since in its shortened form, it looks a lot more like the prefix to the word “number.”

This book takes no position on which of these is the preferred pronunciation, but you should note that both pronunciations are used commonly, and people tend to feel quite strongly about which pronunciation is the “correct” one. Caveat coder.

In Kotlin, as in many other programming languages, an enum is its own specialized type, indicating that something has a number of possible values.

One big difference in Kotlin is that enums are made by creating an enum class. You get a number of interesting pieces of functionality that enums in other languages don’t necessarily have. As you work through this chapter, you’ll learn about some of the most commonly-used bits of functionality and how to take advantage of them as you work in Kotlin.

To get started, open the starter project for this chapter and dig in.

## Creating your first enum class

Open up main.kt. Above the `main()` function, define a new enum class:

``````enum class DayOfTheWeek {
// more code goes here
}
``````

Next, replace the comment by adding a comma-separated list of cases, or individual values, for the day of the week:

``````Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
``````

In the `main()` function, replace the existing `println()` statement with one which goes through and prints out some information you get for free with any enum class:

``````for (day in DayOfTheWeek.values()) {
println("Day \${day.ordinal}: \${day.name}")
}
``````

Run the updated `main()` function, and it should print out the following:

``````Day 0: Sunday
Day 1: Monday
Day 2: Tuesday
Day 3: Wednesday
Day 4: Thursday
Day 5: Friday
Day 6: Saturday
``````

Neat! So what did you just get for free from Kotlin by declaring `DaysOfTheWeek` to be an enum class?

• The `values()` companion function on the enum class gives you a `List` of all the declared cases in the class, making it easy to go through all possibilities, and also to find out how many possibilities exist.
• The `ordinal` property of each case gives that case’s index in the list of declared cases. You’ll note from what’s printed out that the order is zero-indexed.
• The `name` property of each case takes the name of the case in code and gives back the String value of that name.

A lot of this behavior is possible because enum classes are, well, classes. Each case is an instance of the class, so things like compiler-generated companion object functions for the class itself and individual properties for each instance are possible. Additionally, because these properties return objects of their own, you can use other functionality like getting the day based on a passed-in integer index.

For example, let’s say your colleagues working somewhere else in the code tell you that they’ll hand you an integer representing the day of the week. You could use the functionality of `List`, with which you’re already familiar, to get the value at the appropriate index.

Add the following to the `main()` function to see this in action:

``````val dayIndex = 0
val dayAtIndex = DayOfTheWeek.values()[dayIndex]
println("Day at \$dayIndex is \$dayAtIndex")
``````

Run the `main()` function again, and at the end, you’ll see:

``````Day at 0 is Sunday
``````

If you want, you can even change the index of the day to update the value returned. Make sure not to go beyond the length of `values()`, as that will throw an `ArrayIndexOutOfBoundsException`, just like it will with any other list in Kotlin.

Another nice piece of functionality you get for free is the `valueOf()` method, which takes a `String` and returns the enum instance matching that string.

Add the following to the bottom of the `main()` function:

``````val tuesday = DayOfTheWeek.valueOf("Tuesday")
println("Tuesday is day \${tuesday.ordinal}")
``````

Run the `main()` function, and at the end of the output you’ll see:

``````Tuesday is day 2
``````

Neat! Now, the eagle-eyed among you may have noticed that the `valueOf()` function doesn’t return a nullable. So what happens when you try to get the value of an enum case that doesn’t exist? Let’s find out.

Add the following lines to the `main()` function:

``````val notADay = DayOfTheWeek.valueOf("Blernsday")
println("Not a day: \$notADay")
``````

Run `main()` again, and:

``````Exception in thread "main" java.lang.IllegalArgumentException: No enum constant DayOfTheWeek.Blernsday
at java.lang.Enum.valueOf(Enum.java:238)
at DayOfTheWeek.valueOf(main.kt)
at MainKt.main(main.kt:23)
``````

Nooooo! Weren’t Kotlin’s nullables supposed to save us from these “thing doesn’t exist” exceptions!?

The designers of Kotlin decided that trying to access an enum case which doesn’t exist, akin to accessing an index outside the bounds of an array, was enough of an error that an exception should be thrown. So that stopped your process dead in its tracks.

Delete the last two lines you added looking for “Blernsday” so the rest of your code runs.

### Updating case order

Another nice thing about enum classes is that if you find out something needs to be in a different order from a zero-indexed perspective, it’s easy to make that change.

``````Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday;
``````
``````Day 0: Monday
Day 1: Tuesday
Day 2: Wednesday
Day 3: Thursday
Day 4: Friday
Day 5: Saturday
Day 6: Sunday
Day at 0 is Monday
Tuesday is day 1
``````

## Enum class properties and functions

Like other classes, enum classes can have properties and functions. You can even set them up to be passed in as part of the constructor for each case.

``````enum class DayOfTheWeek(val isWeekend: Boolean)
``````
``````Monday(false),
Tuesday(false),
Wednesday(false),
Thursday(false),
Friday(false),
Saturday(true),
Sunday(true);
``````
``````println(
"Day \${day.ordinal}: \${day.name}, is weekend: \${day.isWeekend}"
)
``````
``````Day 0: Monday, is weekend: false
Day 1: Tuesday, is weekend: false
Day 2: Wednesday, is weekend: false
Day 3: Thursday, is weekend: false
Day 4: Friday, is weekend: false
Day 5: Saturday, is weekend: true
Day 6: Sunday, is weekend: true
``````
``````enum class DayOfTheWeek(val isWeekend: Boolean = false)
``````
``````Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday(true),
Sunday(true);
``````
``````companion object {
fun today(): DayOfTheWeek {
// Code goes here
}
}
``````
``````// 1
val calendarDayOfWeek = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
// 2
var adjustedDay = calendarDayOfWeek - 2
// 3
val days = DayOfTheWeek.values()
if (adjustedDay < 0) {
}
// 4
val today = days.first { it.ordinal == adjustedDay }
``````
``````val today = DayOfTheWeek.today()
val isWeekend =
"It is\${if (today.isWeekend) "" else " not"} the weekend"
println("It is \$today. \$isWeekend.")
``````
``````It is Monday. It is not the weekend.
``````
``````fun daysUntil(other: DayOfTheWeek): Int {
// 1
return if (this.ordinal < other.ordinal) {
// 2
other.ordinal - this.ordinal
} else {
//3
other.ordinal - this.ordinal + DayOfTheWeek.values().count()
}
}
``````
``````val secondDay = DayOfTheWeek.Friday
val daysUntil = today.daysUntil(secondDay)
``````
``````println("It is \$today. \$isWeekend. There are \$daysUntil days until \$secondDay.")
``````
``````It is Monday. It is not the weekend. There are 4 days until Friday.
``````

## Using when with enum classes

One of the most powerful features of enum classes is how they combine with the `when` expression. You’ve already seen how this can be used on basic types like `Int` and `String`.

``````when (today) {
DayOfTheWeek.Monday -> println("I don't care if \$today's blue")
DayOfTheWeek.Tuesday -> println("\$today's gray")
DayOfTheWeek.Wednesday -> println("And \$today, too")
DayOfTheWeek.Thursday -> println("\$today, I don't care 'bout you")
DayOfTheWeek.Friday -> println("It's \$today, I'm in love")
DayOfTheWeek.Saturday -> println("\$today, Wait...")
DayOfTheWeek.Sunday -> println("\$today always comes too late")
}
``````
``````I don't care if Monday's blue
``````
``````else -> println("I don't feel like singing")
``````

``````I don't feel like singing
``````
``````It's Friday, I'm in love
``````

``````It's Friday, I'm in love
``````

## Sealed classes vs. enum classes

As you saw briefly in the previous chapter, a sealed class has a limited number of direct subclasses, all defined in the same file as the sealed class itself. It’s known as `sealed` as opposed to `final`, since although some subclassing is permitted (and in fact, required, as you’ll see in a moment), the subclassing is extremely limited in scope.

### Creating a sealed class

Imagine you’re working for a company that mostly works in U.S. dollars, but also accepts payments in Euros and some form of cryptocurrency.

``````sealed class AcceptedCurrency {
class Dollar: AcceptedCurrency()
class Euro: AcceptedCurrency()
class Crypto: AcceptedCurrency()
}
``````
``````val currency = AcceptedCurrency.Crypto()
println("You've got some \$currency!")
``````
``````You've got some AcceptedCurrency\$Crypto@76ed5528!
``````
``````val name: String
get() = when (this) {
is Euro -> "Euro"
is Dollar -> "Dollars"
is Crypto -> "NerdCoin"
}
``````
``````println("You've got some \${currency.name}!")
``````
``````You've got some NerdCoin!
``````
``````sealed class AcceptedCurrency {
abstract val valueInDollars: Float
class Dollar: AcceptedCurrency() {
override val valueInDollars = 1.0f
}
class Euro: AcceptedCurrency() {
override val valueInDollars = 1.25f
}
class Crypto: AcceptedCurrency() {
override val valueInDollars = 2534.92f
}
// leave the existing name property alone
}
``````
``````var amount: Float = 0.0f
``````
``````fun totalValueInDollars(): Float {
return amount * valueInDollars
}
``````
``````currency.amount = .27541f
println("\${currency.amount} of \${currency.name} is "
+ "\${currency.totalValueInDollars()} in Dollars")
``````
``````0.27541 of NerdCoin is 698.1423 in Dollars
``````

## Enumeration as state machine

A state machine is essentially an exclusive list of possible states for a given system. Using an enum can make it more clear to the caller what state the system is in at any point.

``````enum class DownloadState {
Idle,
Starting,
InProgress,
Error,
Success
}
``````
``````Downloader().downloadData("foo.com/bar",
//TODO
},
completion = { error, list ->
// TODO
})
``````
``````error?.let { println("Got error: \${error.message}") }
list?.let { println("Got list with \${list.size} items") }
``````
``````when (downloadState) {
}
``````
``````"Downloading" from URL: foo.com/bar
[etc...]
Got list with 100 items
``````
``````"Downloading" from URL: foo.com/bar
[etc...]
``````

## Nullables and enums

Enums can also be dealt with at both the `when` level and as part of an API with nullability. In the `Downloader` class, instead of having an `Idle` option in `DownloadState`, you could express that nothing was happening by allowing the download state to be optional.

``````var downloadState: DownloadState? = null
``````
``````fun downloadData(
fromUrl: String,
completion: (error: Error?, data: List<Int>?) -> Unit
) {
// rest of method unchanged
}
...
private fun postProgress(
) {
// rest of method unchanged
}
``````

``````when (downloadState) {
/// rest of when unchanged
}
``````
``````"Downloading" from URL: foo.com/bar
``````

## Key points

• Enum classes are a powerful tool for handling situations where a piece of data will (or at least should) be one of a defined set of pre-existing values. Enum classes come with a number of tools for free, such as getting a list of all the declared cases, and the ability to access the order and names of the cases.
• Sealed classes are a powerful tool for handling situations where a piece of data will (or at least should) be one of a defined set of pre existing types.
• Both enum classes and sealed classes let you take advantage of Kotlin’s powerful `when` expression to clearly outline how you want to handle various situations.
• Enum classes are particularly useful for creating, updating, and cleaning information about the current state in a state machine.

## Where to go from here?

There are a few more places where you can learn more about enum classes and sealed classes:

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now