Kotlin Enum (A Complete story of Enumeration)

Abhishek Srivastava
10 min readAug 26, 2022

--

let’s start to know about the Kotlin Enumeration which has lots of more power than any other programming language.

Although enumerations usually represent just a mere named list of predefined constant values, Kotlin enums are much more than that. In fact, they are real classes, and not simple types or limited data structured.

This translates to the fact that they can have custom properties and methods, implement interfaces, use anonymous classes, and much more. Thus, Kotlin enum classes play a crucial role in the language. So kotlin provide feature to developer to make code more easier and less error-prone.

Diff between Kotlin enum classes and Java enum types

In Java, enums are types. Specifically, the official documentation defines an enum type as “a special data type that enables a variable to be a set of predefined constants.” This means that the aforementioned variable must be equal to one of the predefined values. These values are constants, and represent the properties of the enum type.

Despite being a type, the Java enum declaration actually creates a class behind the scenes. Thus, Java enums can include custom methods and properties. This, in addition to the default ones automatically added by the compiler. That’s it — nothing more can be done with Java enum types.

In kotlin enum class is native as compare to Java that’s why kotlin enum called as class not as a type in java. Kotlin enums are much more than that, Not only can they use anonymous classes, but also implement interfaces, just like any other Kotlin class. So, let’s forget Java enum types and start developing into Kotlin enum classes.

Example

Enum classes can also declare members (i.e. properties and functions). A semicolon (;) must be placed between the last enum object and the first member declaration.

If a member is abstract, the enum objects must implement it.

enum class Color {
RED {
override val rgb: Int = 0xFF0000
},
GREEN {
override val rgb: Int = 0x00FF00
},
BLUE {
override val rgb: Int = 0x0000FF
}
;
abstract val rgb: Int fun colorString() = "#%06X".format(0xFFFFFF and rgb)
}

Let’s start from the Basic of Enum

Defining enums

The most basic use case for Kotlin enum classes is to treat them as collections of constants. In this case, they are called type-safe enums and can be defined as follows:

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

As you can see, the enum keyword is followed by the class keyword. This should prevent you from being fooled into thinking that Kotlin enums are mere types.

Then comes the enum class name. Finally, inside the body of the enum class, the possible comma-separated options called enum constants. Note that since they are constants, their names should always be in uppercase letters. This is what the simplest Kotlin enum class consists of.

Initializing enums

Kotlin enums are classes, which means that they can have one or more constructors. Thus, you can initialize enum constants by passing the values required to one of the valid constructors. This is possible because enum constants are nothing other than instances of the enum class itself.
Let’s see how this works through an example:

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

This way, each enum constant is associated with the relative number of the day of the week.

Usually, the constructor-based approach is used to provide enum constants with useful information or meaningful values. On of the most common cases is to provide them with a custom printableName property. This is very useful when printing them, and can be achieved as follows:

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

Inbuilt properties

Kotlin enum classes come with a few inbuilt properties. Just like what happens in Java, they are automatically added to each enum class by the compiler. So, you can access them in any enum class instance. Let’s see them all:

  1. ordinal
    ordinal allows you to retrieve where the current enum constant appears in the list. It is a zero-based index, which means that the first constant in the options list has value 0, the second 1, and so on. When implementing the Comparable interface, this property will be used in sorting logic.
  2. name
    name returns the name of the enum constant as a string.
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)")
}

By running this code, you would get the following result:

[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)

As you can see, the string returned by the name inbuilt property coincides with the constant itself. This does not make them very printable, and this is why adding a custom printableName property might be useful.

Also, this example highlights why adding a custom index might be required as well. This is because ordinal is a zero-based index meant to be used while programming rather than to provide informational content.

Disadvantage of Enum in kotlin

Kotlin Enum could not support inheritance. Enums can implement an interface. That means to extend it you would simply add another enum implementing the same interface.

  1. Lets say you have an error. This error has an error code. Default error are implemented as DefaultError enum and can be extended by adding aditional enums implementing the Error interface.
interface Error {
fun code(): Int
}
enum class DefaultError(private val code: Int) : Error {
INTERNET_ERROR(1001),
BLUETOOTH_ERROR(1002),
TEMPERATURE_ERROR(1003);
override fun code(): Int {
return this.code
}
}
enum class NfcError(private val code: Int) : Error {
NFC_ERROR(2001);
override fun code(): Int {
return this.code
}
}
enum class KameraError(private val code: Int) : Error {
CAM_ERROR(3001);
override fun code(): Int {
return this.code
}
}

2. Each value in an ENUM is an object and each declaration will take some runtime memory simply to reference the object. So ENUM values will take more memory then Integer or String constant. Even in old android devices (<=2.2), there were some performance issue related to ENUM which were solved in JIT compiler.

Adding a single ENUM will increase the size (13x times than the Integer constant) of final DEX file. It also generates the problem of runtime overhead and your app will required more space.

So overuse of ENUM in Android would increase DEX size and increase runtime memory allocation size.

If your application is using more ENUM then better to use Integer or String constants instead of ENUM. But still problem remains…

Solution

Android provide annotation library which has TypeDef annotations. These annotations ensure that a particular parameter, return value, or field references a specific set of constants. They also enable code completion to automatically offer the allowed constants.

IntDef and StringDef are two Magic Constant Annotation which can be used instead of Enum. These annotation will help us to check variable assignment like Enum in compile time.

Advanced features of Kotlin enums

Now discuss about the most advanced and complicated features offered by Kotlin enum classes.

Adding custom properties and methods

Custom properties and methods can be added to enum classes, just like in any other Kotlin class. What changes is the syntax, which must follow specific rules.

In particular, methods and properties must be added below the enum constants definition, after a semicolon. Just like any other property in Kotlin, you can provide them with a default value. Plus, enum classes can have both instance and static methods:

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"
}
}

In this example, a custom constructor, two custom properties, and a custom instance method were added to the enum class. Properties and methods can be accessed through instances, which are the enum constants, with the following syntax:

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

Keep in mind that Kotlin does not have the concept of static methods. However, the same result can be achieved by harnessing companion objects, which are supported by Kotlin enum classes. Methods defined inside companion objects do not depend on specific instances and be accessed statically. You can add one as follows:

enum class Day {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY;
companion object {
fun getNumberOfDays() = values().size
}
}

Now, you can access the getNumberOfDays() method this way:

Day.getNumberOfDays()

As you can see, the method is called statically on the class and does not depend on any instance. Note that the synthetic static method values() was used while implementing it. You are going to see what it is and how to use it very soon.

Using anonymous classes to define enum constants

We can create anonymous classes to define specific enum constants. In contrast to Java, Kotlin enum classes support anonymous classes.

In particular, enum constants can be instantiated by anonymous classes. They just have to give an implementation to the abstract methods of the enum class itself. This can be achieved with the following syntax:

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
}

As shown here, each enum constant is instantiated by declaring its own anonymous classes while overriding the required abstract method. This is just as it would happen in any other Kotlin anonymous class.

Enums can implement interfaces

Although Kotlin enum classes cannot derive from a class, enum class, or an abstract class, they can actually implement one or more interfaces.

In this case, each enum constant must provide an implementation of interface methods. This can be achieved with a common implementation, as follows:

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

Or by using anonymous classes as showed before:

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
};
}

In both cases, each enum constant has the IDay interface method implemented.

Enums in action

Now that you have seen both basic and advanced features, you have everything required to start using Kotlin enum classes. Let’s see them in action through the three most common use cases.

1. Enums and when

Enum classes are particularly useful when used with Kotlin’s when conditional statement. This is because when expressions must take each possible condition into account. In other words, they must be exhaustive.

Since enums offer a limited set of values by definition, Kotlin can use this to figure out if every condition was considered. If not, an error at compile time will be thrown. Let’s see enums in action with the when expression:

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")
}

As just shown, enums allow you to differentiate logic based on their value. They also make your code more readable and less error-prone. This is because they establish the maximum number of possible options to be considered in a when statement. This way, you cannot forget one.

2. Enums and Kotlin synthetic methods

Similar to the aforementioned inbuilt properties, every enum class also has synthetic methods. They are automatically added by Kotlin at compile time and represent utility functions that can be accessed statically. Let’s see the most important ones and how to use them:

  • values()
    It returns the list of all the enum constants contained within the enum class.
  • valueOf(value: String)
    It returns the enum constant whose name property matches the value string passed as a parameter. If not found, an IllegalArgumentException is thrown.

Let’s see them in action through an example:

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)
}

When run, the following result would be printed:

Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
Monday

Note that the same result can be obtained by employing the two following Kotlin global functions: enumValues<T>() and enumValueOf<T>(). They allow you to access any enum class T with a generic-based approach.

3. Iterating through enums

Finally, both use cases can be combined to iterate through them thanks the values() synthetic method and execute different actions based on their value with a when expression. Let’s look at an example based on this approach:

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")
}

This way, custom logic can be executed based on each of the current possible values of which the enum class consists of. If launched, the snippet would return this:

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

Conclusion

Whenever you need to group constants together, you should consider using a kotlin enum. Doing so will make your code more readable and it will even offer protection against bugs through compiler checks. Since kotlin enum classes are just like any other class, they are free to declare attributes, methods, and even implement interfaces. For this reason, they are highly flexible and can be used to develop robust, readable, and loosely coupled code.

Enums also work great with kotlin when since the compiler will check and make sure that all cases of the enumeration are covered. This will help you maintain your code later on as you add values or remove them from your enum class. The kotlin compiler will force you to cover all cases of the enum or add an else block to it.

--

--

Abhishek Srivastava

Senior Software Engineer | Android | Java | Kotlin | Xamarin Native Android | Flutter | Go