In the last chapter, we created our very own type, called Circle, using a feature in Kotlin called a class.
In this chapter, we’re going to look at a special kind of class—an enum class—which is particularly useful when we want to represent a limited number of values.
I hope you like Schnauzers, because this chapter is full of them!
Limiting the Values
Schnauzers are amazing dogs—they’re smart, they’re loyal to their owners, and they seem to have an opinion about everything!
Let’s create a String variable to hold the name of a pet Schnauzer.
val nameOfSchnauzer: String = "Shadow"How many different names for Schnauzers can you think of? Shadow, Rover, and Captain Fluffybeard come to mind, but there’s really no limit to the variety of names that could be given to them. The possibilities are unlimited!
Schnauzer breeds, however, are a different story! There are only 3 breeds of Schnauzer.
- Miniature Schnauzer
- Standard Schnauzer
- Giant Schnauzer
So, while the number of names for a Schnauzer is unlimited, the number of breeds is limited to just 3 options.
How does all of this relate to programming?
Well, when we’re writing code, we can reduce the likelihood of errors by choosing a type that limits the range of possible values to only those that are valid.
For example, a
Stringcan hold practically any text you can imagine. Since you can name your dog almost anything imaginable, it would make sense to use aStringto hold its name.On the other hand, a
Stringwould not be a great choice to hold the specific breed of a Schnauzer. Why? Because strings can hold an infinite number of different values, but there are only 3 possibilities that could be correct for a Schnauzer breed. In other words, when using aString, there’s no way to guarantee that we will set a breed variable to one of those three correct values.To demonstrate this, let’s say we decided to use a
Stringto represent the breed, like this.val breedOfSchnauzer: String = "Miniature"Now, somewhere else in our code, we might have a conditional that’s checking the breed.
if (breedOfSchnauzer == "Mini") { // ... do something }We might have intended that the conditional expression would evaluate to
true, but since we incorrectly typed"Mini"instead of"Miniature", it would have evaluated tofalse… and we wouldn’t have known that anything was wrong until we ran the code!The problem is that we used a type that allows unlimited values (that is, the
Stringtype), when we needed a type that allows only a limited number of values. If we could create a type that only allows one of the three breed names, the error above wouldn’t even be possible!To create a type with limited values in Kotlin, we use an enumeration class, or as we more often refer to it, an enum class, or just an enum.
Creating an Enum Class
Let’s create an enum class to represent Schnauzer breeds.
enum class SchnauzerBreed { MINIATURE, STANDARD, GIANT }This enum class is named
SchnauzerBreed, and it gives us three breed options to choose from. Here are the main parts of anenum class.
To create an enum class, we write
enum classrather than justclass.
After that comes the name that we want to give our class—in this case,
SchnauzerBreed.Inside the body of the class, the options are called enum entries. Sometimes we call them enum constants instead. They’re separated with commas, and by convention, they’re written in all capital letters.
Using an Enum Class
Now that we have an enum class, we can start using it. Note that we will not construct an object from an enum class in the same way that we would do with normal classes. If we try, we’ll get an error.
val breed: SchnauzerBreed = SchnauzerBreed()ErrorInstead, we just assign a variable to one of the entries, like this.
val breed: SchnauzerBreed = SchnauzerBreed.GIANTBy using an enum class here, if we were to accidentally type
MINIinstead ofMINIATURE, then Kotlin would give us an error immediately—before we even run the code!val breed: SchnauzerBreed = SchnauzerBreed.MINIErrorSo instead of using a
String, which can be set to just about anything, we limited the valid options to just three enum entries. By doing that, Kotlin can now help tell us when we typed something wrong, without having to even run the code!Using Enum Classes with
whenExpressionsAnother nice benefit of using enum classes is that Kotlin provides some added assistance when we use them with a
whenexpression.As you might recall,
whenexpressions must account for every condition—that is, they must be exhaustive. Because enum classes limit the possible values, Kotlin can use this information to know when we have indeed accounted for every condition.For example, here’s some code that returns a description of a breed.
fun describe(breed: SchnauzerBreed) = when (breed) { SchnauzerBreed.MINIATURE -> "Small" SchnauzerBreed.STANDARD -> "Medium" SchnauzerBreed.GIANT -> "Large" }Notice that there’s no
elsecondition here! Kotlin can tell that we’ve included all three of the enum entries in thewhenbody, so there are no other possibilities.If we were to omit some of the entries, Kotlin would give us an error.
fun describe(breed: SchnauzerBreed) = when (breed) { SchnauzerBreed.MINIATURE -> "Small" SchnauzerBreed.STANDARD -> "Medium" }ErrorTo remedy this error, we would either have to provide all of the enum constants, as we did in Listing 5.8, or we’d have to provide an
elsecondition, like this.fun describe(breed: SchnauzerBreed) = when (breed) { SchnauzerBreed.MINIATURE -> "Small" SchnauzerBreed.STANDARD -> "Medium" else -> "Unknown" }It’s great that Kotlin will give us an error when our
whenis not exhaustive. For example, whenever we add a new entry to an existing enum class, Kotlin will give us errors in all of thewhenexpressions that need to be updated—so we can know exactly what parts of our code need to be fixed!Adding Properties and Functions to Enum Classes
As you recall from the last chapter, normal Kotlin classes allow us to put properties and functions together. Well, it’s also possible to add properties and functions to an enum class.
Let’s start by adding a property as a constructor parameter. We can do this just as we did with normal classes—by putting them between the opening
(and closing)parentheses after the name of the class.For example, we might want to include the approximate height of each breed, in centimeters. We can do that like this.
enum class SchnauzerBreed(val height: Int) { MINIATURE(33), STANDARD(47), GIANT(65) }Because we added a new constructor parameter called
height, we also had to add a constructor argument to each of the enum entries. So a miniature has an approximate height of 33 cm, a standard is about 47 cm, and a giant is about 65 cm tall.Note that the enum entries are in fact instances of the enum class.
We can get a property off of an enum instance just like we would do with regular objects. For example, we can print the height of a breed to the screen with
println().println(SchnauzerBreed.MINIATURE.height)We can also include a property that does not require a constructor argument. Let’s add a property that tells us the
familyof breeds that they all belong to.enum class SchnauzerBreed(val height: Int) { MINIATURE(33), STANDARD(47), GIANT(65); val family: String = "Schnauzer" }The new
familyproperty is added to the end of the class body. Notice the semicolon after the last entry!It’s just as easy to add a function. As above, be sure to include the semicolon, and then simply write the function beneath the enum entries.
enum class SchnauzerBreed(val height: Int) { MINIATURE(33), STANDARD(47), GIANT(65); val family: String = "Schnauzer" fun isShorterThan(centimeters: Int) = height < centimeters }When using an enum class, we can get properties and call functions just as we would with any other class.
println(SchnauzerBreed.STANDARD.family) println(SchnauzerBreed.STANDARD.isShorterThan(40))Built-In Properties
In addition to any properties that you include yourself, Kotlin also automatically adds a couple of properties to all enum instances. Before we wrap up this chapter, let’s take a look at those.
ordinalEach enum entry has a property named
ordinal, which tells us its position—~where this enum entry appears in the list_. The first entry in the list has an ordinal of 0, the second has an ordinal of 1, the third has an ordinal of 2, and so on. For example, sinceSTANDARDis the second entry in the list, it has an ordinal of 1.Yes, it starts with zero, which can be a bit confusing for those who haven’t done much programming before. Once we get to the topic of collections in Chapter 8, we’ll see how zero-based numbering is used a lot in programming!
nameWhen we need to know the name of the enum entry as a string, we can use the
nameproperty. To demonstrate this, let’s create a function that tells us thenameof the breed, along with theheight.fun describe(breed: SchnauzerBreed) { println(breed.name) println(breed.height) }Now, we can call
describe(SchnauzerBreed.STANDARD), and we’ll see this on the screen.STANDARD 47There’s more than this! We can also get all of the
entriesout of an enum class. However, this won’t be helpful until we learn how to iterate over values, which we’ll cover in Chapter 8. For now, let’s wrap up this chapter!Summary
Here’s what we learned in this chapter:
- How limiting the range of values can help make sure our programs are written correctly.
- How to create an enum class.
- How to use enum classes with the
whenconditional.- How to add properties and functions to an enum class.
- How to use some built-in properties of enum classes.
At this point, we’ve created lots of variables, each one holding a value. But what about when we need to handle the absence of a value? In the next chapter, we’ll learn about nulls and the tools that Kotlin provides to handle them safely!
Thanks to James Lorenzen and Mohit for reviewing this chapter!