Enumerated Classes with Associated Data in Kotlin: The Definitive Guide

- Andrés Cruz

En español
Enumerated Classes with Associated Data in Kotlin: The Definitive Guide

Enumerations are a special data type that allows you to set predefined or constant values in variables that are used in other programming languages like Java and that we can also use in Kotlin in a very similar way as in other programming languages like Java; although with another syntax when defining it; Let's remember that in Java we must declare our enumerated type or enum in the following way:

public enum Planetas {
    MERCURIO,
    VENUS,
    TIERRA,
    MARTE,
    JÚPITER,
    SATURNO,
    URANO,
    NEPTUNO;
}

Although normally enum types can have some type of data or information contained within them; to be something like the Pair in Kotlin that we discussed in a previous post; in Java or the Java form would be like the following:

public enum Planetas {
    MERCURIO(1),
    VENUS(2),
    TIERRA(3),
    MARTE(4),
    JÚPITER(5),
    SATURNO(6),
    URANO(7),
    NEPTUNO(8),
    PLANETAX(9);
}

In Kotlin, as we saw in the case of Classes in Kotlin, we can take everything in its minimum expression and indicating its type and name is enough to create an enumerated or emun type:

enum class NombreEnumerado

But the previous code in this way is very useless to fix this we have to open the braces and indicate all the possible values for our enumeration; for example:

enum class Planetas {
	MERCURIO, VENUS, TIERRA, MARTE, JUPITER, SATURNO, URANO, NEPTUNO
}

As you can see, it is an object where it defines each of its constants separated by commas.

In these cases, when we access some of the values defined in the previous emun; for example Planets.URANUS this would return the name of the planet as output, in this case a text in uppercase that represents the name of the planet:

"URANO"

We can even have different types in the enumerated classes, although it is a bit strange, surely you can find more than one utility:

    MERCURIO(1),
    VENUS(2),
    TIERRA(3),
    MARTE(4),
    JÚPITER(5),
    SATURNO(6),
    URANO(7),
    NEPTUNO(8),
    PLANETAX("EXISTE O ESTÁ EN OTRA POSICIÓN");

Another possible use is an event handler where you are working with different data, for example, for your model you would need to have:

  • Title: text type
  • Content: text type
  • Image: can be a boolean type
  • Date: a datetime might work fine

So, if for some reason we wanted to use a Kotlin enum, we would have to handle different types of data, although for this example the data class in Kotlin could also work well for us, but it all depends on how you have conceptualized your application; another example would be a Chat message that would be similar to the previous example; we can further enhance this by defining our own values along with the enums as we'll see in the next section.

Defining constant values in emuns using a constructor

Each of the definitions of the enumerated types are constant values, so to initialize them you have to indicate a value after indicating the name of it in parentheses and define a constructor method as we did before:

enum class Planetas(val orden: Int) {
	MERCURIO(1), VENUS(2), TIERRA(3), MARTE(4), JUPITER(5), SATURNO(6), URANO(7), NEPTUNO(8)
}

As you can see, the syntax is still very clean, with a few more additions and we continue to use the enum class as before, being a constructor method, we can specify as many parameters as we want:

enum class Planetas(val n: Int, val tamano: Float) {
   MERCURIO(1,1F), VENUS(2,2F), TIERRA(3,2F), MARTE(4,1.5F), JUPITER(5,2600F), SATURNO(6,1500F), URANO(7,20F), NEPTUNO(8,20F)
}

Although this step (initialize the values) is optional and in many other cases, depending on the model we want to use, we don't have to place it; for example, if we only need a simple reference as we specified at the beginning or in the following example:

 
enum class Persona {
	NOMBRE, APELLIDO, EDAD, TAMANO, SEXO
}

Or the same example that we can find on the Kotlin website:

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

As you can see, enum data types are very useful when we want to classify different data models, but for which we don't need to perform operations using methods or other elements, just a mere reference and at most a value that accompanies it.

Using the emuns in Kotlin from our code

Now, in order to use some of the enums we saw earlier; for example, that of the planets of the solar system:

enum class Planetas(val orden: Int) {
   MERCURIO(1), VENUS(2), TIERRA(3), MARTE(4), JUPITER(5), SATURNO(6), URANO(7), NEPTUNO(8)
}
val planeta = Planetas.MARTE

val colorPlaneta = when(planeta) {
   Planetas.TIERRA -> "tierra"
   Planetas.MARTE -> "rojo"
   else -> "Otros colores"
}

println(colorPlaneta)

Overriding methods in emums

We can create our own methods and even override existing ones, for example the toString() method as follows:

enum class Planetas {
	MERCURIO, VENUS, TIERRA, MARTE, JUPITER, SATURNO, URANO, NEPTUNO;

    override fun toString(): String {
        return super.toString().toUpperCase()
    }
}

Defining abstract methods on enums

And this would be all with the enumerated types in Kotlin, you can have more information in the following link: Kotlin: Enum Classes

We can define static methods but in the Kotlin way, with the companions within the same enum so that it performs, for example, a printout of all the values of the enumerations:

enum class UserEnum {
    USER_ID,
    USERNAME,
    NAME,
    SURNAME,
    EMAIL,
    AUTH_LEVEL,
    AVATAR,
    COUNT_NOTIFICATION;

    companion object {
        inline fun <reified T : Enum<T>> colString() {
            print(enumValues<T>().joinToString { it.name.toLowerCase() })
        }

    }
}

fun main(args: Array<String>) {
    UserEnum.colString<UserEnum>() // print user_id, username, name, surname, email, auth_level, avatar, count_notification
}

Or we can extend the function without needing to implement it inside the enumerated type:

enum class RGB { RED, GREEN, BLUE }

inline fun <reified T : Enum<T>> printAllValues() {
    print(enumValues<T>().joinToString { it.name })
}
fun main(args: Array<String>) {
    printAllValues<RGB>() // prints RED, GREEN, BLUE
}

Anonymous Classes: Supertypes, Overrides, and Abstract Methods in Kotlin:

We can define constant values in enums through anonymous classes and functions as shown below; they are ideal for showing or performing some calculation that has to do with the rest of the structure that we perform in the enumerable types and this is another proof of how versatile and consistent data structures are in Kotlin:

enum class Interes {
    SILVER {
       override fun intereses() = 0.25f
    },
    GOLD {
        override fun intereses() = 0.5f
    };
 
    abstract fun intereses(): Float
}

fun main(args: Array<String>) {
    val cashbackPercent = Interes.SILVER.intereses()
    print(cashbackPercent) // print 0.25
}

Enumerated classes to implement interfaces

The interfaces are a type of mechanism that we have in the classes as we discussed in a previous entry that we talked about the types of the classes; the interfaces are also available in the enumerations and we can use them in the following way:

interface Interes  {
       override fun intereses(): Float
}

enum class Interes {
    SILVER {
       override fun intereses() = 0.25f
    },
    GOLD {
        override fun intereses() = 0.5f
    };
 
    abstract fun intereses(): Float
}

fun main(args: Array<String>) {
    val cashbackPercent = Interes.SILVER.intereses()
    print(cashbackPercent) // print 0.25
}

As you can see, we declare an interface called Interest with a method which we then override in the enum that has the same name as the interface.

Iterating over enumerated data types

In the same way, it is very easy to iterate our constants defined in the enums, by indicating the enum as if it were an array, then the values function that returns the collection of the enums and we take the value of the enum with the name property:

for (userEnum  in UserEnum.values()) { 
println(userEnum.name) 
}

As you can see, in this way we can iterate through the enums as an array, this is useful since many times we need to process the enum as a whole, compare its values in a direct way and that is what the value method is for; if you want to know the size:

 UserEnum.values().size

If you want to search directly inside the collection of enumerations, passing as a parameter a string that matches (or doesn't) match the constant:

 UserEnum.valueOf("NAME")

To know the position of a constant within the collection of enumerations:

println(UserEnum.USERNAME.ordinal) // print 1

Listed within other listed

We can also embed enumerations within the other, and we can apply everything seen so far at any level; for example, an enum inside another enum:

enum class Meses {
    Enero,
    Febrero;

    enum class Dias{
        Lunes,
        Martes,
        Miercoles
    }
}
    println(Meses.Enero)
    println(Meses.Dias.Lunes)

Conclusion

In this post we saw how powerful and versatile Kotlin enumerations are, a good guide that will allow you to perform the most common operations with Kotlin enumerations; know basic concepts and initialize constants through constructors, use methods and interfaces, embed enumerations, traverse them as if they were a list or an array, place companion objects, and many other operations.

Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy

Acepto recibir anuncios de interes sobre este Blog.