1. Overview
Enums are a powerful feature in Kotlin. They allow us to define a collection of constants that can be used throughout our application in a type-safe way. Sometimes, we need to iterate over these constants to perform various operations.
In this tutorial, we’ll explore different ways to iterate over Enum entries in Kotlin and demonstrate their usage with practical examples.
2. Introduction to the Problem
As usual, examples can explain a problem quickly. So, let’s first create an Enum class:
enum class Weekday {
MON, TUE, WED, THU, FRI
}
We cannot directly iterate the instances defined in the Weekday Enum using loops:
for (day in Weekday) { } // doesn't compile
or through forEach():
WeekDay.forEach { } // doesn't compile
To iterate through Enum entries, we need to convert the Enum to a data type that is easy to iterate, such as an Array or a List:
val expectedWeekdayArray = arrayOf(MON, TUE, WED, THU, FRI)
val expectedWeekdayList = listOf(MON, TUE, WED, THU, FRI)
Then, iterating a List or an array isn’t a challenge to us at all:
expectedWeekdayArray.forEach { } // compiles
for (day in expectedWeekdayList) { } // compiles
So next, let’s see how to convert an Enum to an Array or a List that carries all Enum instances. For simplicity, we’ll leverage unit test assertions to verify if we get the expected Array or List.
3. Prior to Kotlin 1.9: Using the values() Function
The values() function is available for all Kotlin Enums. It returns an Array of constants that is defined in the Enum class. Further, the elements in the result Array follow the order they’re declared in the Enum:
val result = Weekday.values()
assertArrayEquals(expectedWeekdayArray, result)
With this result array, we can quickly iterate the Enum‘s entries.
It’s important to note that since Kotlin 1.9, the JetBrains team has introduced *the entries property for all Enums to replace the values() function.*
Next, let’s look at how to work with the entries property.
4. Kotlin 1.9+: Using the entries Property
The JetBrains team detected a hidden performance issue when using an Enum Array and introduced the entries property to replace the Enum‘s values() function.
The entries property is of type EnumEntries:
public sealed interface EnumEntries<E : Enum<E>> : List<E>
As we can see, as EnumEntries is a subtype of List, the entries property is also a List:
val result = Weekday.entries
assertEquals(expectedWeekdayList, result)
Therefore, we can iterate all instances of an Enum through its entries property.
5. Using the enumValues() Function
Additionally, *Kotlin provides the enumValues() generic function to return an Enum‘s instances in an Array*:
public inline fun <reified T : Enum<T>> enumValues(): Array<T>
Next, let’s create a simple test to show how to use it:
val weekdayResult = enumValues<Weekday>()
assertArrayEquals(expectedWeekdayArray, weekdayResult)
As it’s a generic function, we can use the same function for different Enums. Let’s create another Enum class:
enum class Holiday {
Eastern, Christmas, LaborDay, NewYear
}
Next, let’s create an Array to hold all Holiday instances:
val expectedHolidayArray = arrayOf(Eastern, Christmas, LaborDay, NewYear)
Now, if we use Holiday as the type parameter, enumValues() returns the expected Array:
val holidayResult = enumValues<Holiday>()
assertArrayEquals(expectedHolidayArray, holidayResult)
Some may wonder why Kotlin provides the enumValues() function, although the entries property is available for all Enum types. This is because enumValues() is pretty handy when we create generic functions.
Next, let’s understand *enumValues()*‘s power through an example.
6. Using enumValues() in Generic Functions
Let’s say we want to create a function to obtain an Enum instance by a String value. There are several requirements:
- Match the String parameter with Enum instances’ names
- Name matching should be case-insensitive and ignore all whitespaces
- If no match is found, the function returns a null value
- The function must work for any Enum type
To fulfill the last requirement, we need to create a generic function. So, we may come up with this approach:
inline fun <reified E : Enum<E>> findEnumByName(name: String): E? {
return E.entries.firstOrNull { it.name.equals(name.replace(" ", ""), ignoreCase = true) }
}
However, this code doesn’t compile:
Kotlin: Type parameter 'E' cannot have or inherit a companion object,
so it cannot be on the left hand side of dot
Now, if we use the generic enumValues() function, the problem can be solved:
inline fun <reified E : Enum<E>> findEnumByName(name: String): E? {
return enumValues<E>().firstOrNull { it.name.equals(name.replace(" ", ""), ignoreCase = true) }
}
Let’s verify our solution by a test:
val tuesday = findEnumByName<Weekday>("t U e")
assertEquals(TUE, tuesday)
val newYear = findEnumByName<Holiday>("N E W Y E A R")
assertEquals(NewYear, newYear)
val unknown = findEnumByName<Holiday>("Thanksgiving")
assertNull(unknown)
As we can see, the findEnumByName() generic function works for different Enum types.
7. Conclusion
In this article, we’ve explored several ways to convert an Enum to an Array or a List so that we can iterate all its instances. The values() function and the entries property are available for any Enum type and are straightforward to use. In addition, the enumValues() function provides a generic way to return an Array of Enum instances. By mastering these techniques, we can leverage the full potential of Enums in our Kotlin applications.
As always, the complete source code for the examples is available over on GitHub.