In Kotlin, an enum (short for enumerator) is a special kind of predefined Kotlin data type used to store and represent a fixed set of named constant values, often simply referred to as constants. Each constant is an object, meaning they can have properties and methods, these are enums and are useful for representing elements that are fixed (for example days of the week) and don’t change.
In this article we’ll help you unlock the full potential of Kotlin enums for Android app development by exploring the differences with Java, as well as looking at:
- Defining enum classes: Kotlin enums vs Java enums
- Enum classes in Kotlin (methods and properties)
- Using the
when
operator - Custom properties
- Advanced features (interfaces, anonymous and sealed classes)
- Best practices
- Common issues
You’ll notice that the majority of the examples we use in the article are based on traffic signals, that’s because these are a great real-world use case for enums.
If you’re interested in Swift enums for iOS app development, why not check out our article on enums for Swift.
Ok, let’s dive in.
Defining enum classes: Kotlin vs. Java
In Java, we would define an enum using the enum
keyword, in Kotlin however, enums are defined using the Kotlin enum class
. Let’s start by taking a look at how we’d define enums in both Java and Kotlin for comparison.
It’s worth pointing out here that, as they are constant values, enums should be written in uppercase, as is standard practice in most developer languages.
Let’s start with Java and an enum for days of the week:
//---------- Define an enum in Java ----------
public enum WeekDays {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY
}
Now, let’s see how we’d define an enum (this time for directions) in Kotlin:
//---------- Define an enum in Kotlin----------
enum class DirectionTypes {
NORTH, SOUTH, WEST, EAST
}
These two examples demonstrate the basic enum declaration works in Java and Kotlin programming languages.
Enum classes in Kotlin
In Kotlin programming, an enum type is a Kotlin class and classes have properties and methods; so a Kotlin enum class also have properties, constructors, and methods.
There are two properties (ordinal
and name
) and two methods (values
and valueOf
) available in Kotlin enum classes.
Let’s take a closer look…
Properties
We’ll look at the properties first, which are as follows:
-
ordinal
: store the index of a constant, which starts from zero. -
name
: store the name of a constant.
Let’s demonstrate with a simple example:
enum class TrafficSignalType {
RED,
GREEN,
YELLOW
}
fun main() {
for (type in TrafficSignalType.values())
println("${type.ordinal} : ${type.name}")
}
/* Output :
0 : RED
1 : GREEN
2 : YELLOW
*/
Here we’ve declared three constant variables and using the for loop, we’ve printed their index and name. To print an index, we use ordinal
properties, and for names, we use name
properties.
Methods
Now let’s take a look at the methods, which are as follows:
-
values()
: The method is used to return a collection with all the values within the enum class. -
valueOf()
: The method is used to return a particular value with a match string. If the passing string is not matched, then it throws anIllegalArgumentException
.
Let’s demonstrate enum methods and their access with another example:
// An example of values method
enum class TrafficSignalType {
RED,
GREEN,
YELLOW
}
fun main() {
for (type in TrafficSignalType.values())
println("${type.name}")
}
/*
Output:
RED
GREEN
YELLOW
*/
Here you can see how we’d use values()
to list all the enum values.
Now, let’s look at an example of the usage of valueOf()
to return the constant of the specified name if it matches exactly:
// An example of valueOf method
enum class TrafficSignalType {
RED,
GREEN,
YELLOW
}
fun main() {
println("${TrafficSignalType.valueOf("GREEN")}")
}
/*
Output :
GREEN
*/
It’s crucial to remember that the
valueOf()
method is case-sensitive and will return anIllegalArgumentException
if the specified name does not match any of the constants.
Here’s an example of the response when asking for a unknown enum value:
// An example of valueOf method with IllegalArgumentException
enum class TrafficSignalType {
RED,
GREEN,
YELLOW
}
fun main() {
println("${TrafficSignalType.valueOf("orange")}")
}
/*
Output :
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant TrafficSignalType.orange
at java.lang.Enum.valueOf (:-1)
at TrafficSignalType.valueOf (File.kt:-1)
at FileKt.main (File.kt:13)
*/
Great! Now we know how to use the properties and methods associated with Kotlin enums we can dive a little deeper.
Kotlin enums with when
operator
Now let’s take a look at using the when
expression in a Kotlin enum, take a look at the example below:
// An examle of when expression
enum class Traffic {
RED,
GREEN,
YELLOW
}
fun main() {
val currentSignal = Traffic.GREEN
when(currentSignal){
Traffic.RED -> println("Red means STOP")
Traffic.GREEN -> println("Green means GO")
Traffic.YELLOW -> println("Yellow means SLOW DOWN")
}
}
/*
Output :
Green means GO
*/
You’ll notice we haven’t used the else
clause as it’s not needed here. We need to define a condition for when
then, if the condition matches, the case block will execute.
Using an enum to handle this kind of situation (where there are multiple possible options which need to be checked continuously) makes our code more readable and maintainable.
Kotlin enums with custom properties
It’s important to understand that each constant in an enum class acts as an instance of that class, separated by commas. However, unlike other classes, these instances are not created using constructors in the traditional sense.
Let’s look at an example of how we could initialize and access enum constants with custom properties:
enum class TrafficSignalType(val info: String) {
RED("stop"),
GREEN("go"),
YELLOW("slow down")
}
fun main() {
val infoRed = TrafficSignalType.RED.info
println("Red light means : $infoRed")
val infoGreen = TrafficSignalType.GREEN.info
println("Green light means : $infoGreen")
val infoYellow = TrafficSignalType.YELLOW.info
println("Yellow light means : $infoYellow")
}
/* Output :
Red light means : stop
Green light means : go
Yellow light means : slow down
*/
Here we’ve defined an enum class called TrafficSignalType
along with three constant values: RED, GREEN and YELLOW, along with their meanings. Meanings are stored in properties called info
, which are passed as constructor parameters.
Make sure to provide clear meaningful descriptions for all enum constants, especially if they are somewhat obscure and not obvious.
Good job! So, we’ve looked at the basic features and methods of enum classes, now we’ll look at some more advanced techniques.
Advanced features of Kotlin enum class
Now let’s look at some advanced features, such as how to implement interfaces in our enums and using anonymous and sealed classes.
Interfaces
An interface is a collection of abstract methods and properties; in Kotlin we use the interface
keyword to create an interface, as follows:
// CREATE AN INTERFACE IN KOTLIN
interface SpeedLimit {
fun getSpeedLimit(): Int
}
// IMPLEMENT INTERFACE AN ENUM CLASS
enum class Traffic : SpeedLimit {
RED{
override fun getSpeedLimit() = 0
},
GREEN{
override fun getSpeedLimit() = 40
},
YELLOW{
override fun getSpeedLimit() = 20
}
}
fun main() {
val limitRed = Traffic.RED.getSpeedLimit()
println("Red light speed limit: $limitRed")
val limitGreen = Traffic.GREEN.getSpeedLimit()
println("Green light speed limit: $limitGreen")
val limitYellow = Traffic.YELLOW.getSpeedLimit()
println("Yellow light speed limit: $limitYellow")
}
/*
Output:
Red light speed limit: 0
Green light speed limit: 40
Yellow light speed limit: 20
*/
Anonymous classes
Kotlin enum classes support anonymous and each constant overrides an abstract function, which is defined in the same enum class. Let’s look at an example:
// anonymous class example in an enum class
enum class Traffic {
RED{
override fun printSpeed(){
println("Red means STOP")
}
},
GREEN{
override fun printSpeed(){
println("Green means GO")
}
},
YELLOW{
override fun printSpeed(){
println("Yellow means Slow Down")
}
};
abstract fun printSpeed()
}
fun main() {
Traffic.RED.printSpeed()
}
/*
Output :
Red means STOP
*/
You’ll notice that in this situation we don’t need to create an interface and implement it.
Sealed classes
Ok, before we work through some best practice and common issues, let’s take a look at how to use a Kotlin sealed class with an enum for type safety. Sealed classes support inheritance and are created using the sealed
keyword, as you can see in the example below:
sealed class Traffic {
class Red:Traffic(){
fun display(){
println("Subclass Red of Sealed class Traffic ")
}
}
class Green:Traffic(){
fun display(){
println("Subclass Green of sealed class Traffic")
}
}
}
fun main(args: Array<String>){
val obj =Traffic.Green()
obj.display()
val obj1=Traffic.Red()
obj1.display()
}
/*
Output:
Subclass Green of sealed class Traffic
Subclass Red of Sealed class Traffic
*/
Sealed classes are particularly useful in more complex scenarios, when extensive customization is required.
It’s worth noting that sealed classes are not available in Java.
Best practices
Below are some best practices and uses cases to consider when using enums in Kotlin, to create more maintainable and efficient code :
- Use enums for sets of constant values: As sets of constant values, enums improve code readability and type safety.
- Use properties and methods: If you need to add additional data to your enum constants then methods and properties are the way to go; just make sure they’re relevant to all constants in your enum to avoid errors.
-
Use
when
operator to handle different enum constants: Thewhen
expression is a efficient and readable way to handle enum-specific logic; remember to include all values so all cases are handled. - Avoid complex logic: While methods and properties allow additional complexity to be added to enums, keep the logic as simple as possible and focused on the purpose of the enum.
- Document all constants: Provide clear documentation and meaningful descriptions for all enum constants, especially if they are somewhat obscure and not obvious.
- Use sealed classes for complex enums: If extensive customization or behaviour is required, sealed classes provide more flexibility and stability
Common issues
Finally, let’s take a look at some of the common issues and pitfalls developers experience when using enums, so that we can avoid falling into the same traps:
- Misuse of enums: The most common mistake developers make is attempting to use enums in situations for which they are unsuitable e.g. for data that is not constant and requires frequent updates. Only use enums for fixed sets of constants and use other solutions for dynamic data sets.
- Too much logic: Enums which include excessively complex logic are much harder to read and maintain and should be avoided.
-
Failing to handle all cases: Not handling all cases when using the
when
operator causes bugs and application functionality failures. - Not using sealed classes: Not using sealed classes in complex scenarios when different data types or methods are needed can lead to significant difficulties.
To sum up
In Kotlin, enums are a powerful feature for representing fixed sets of constants and are best deployed in simple scenarios where complex logic is not required.
We’ve worked through basic usage of enums in Kotlin as well as providing some best practice advice and guidance on how to avoid some of the most common issues.
Hopefully you’ll feel confident to experiment with enums in your projects in the future.
Top comments (0)