Las clases enumeradas con datos asociados en Kotlin: Guía definitiva

- Andrés Cruz

In english
Las clases enumeradas con datos asociados en Kotlin: Guía definitiva

Los enumerados son un tipo de datos especial que permite establecer valores predefinidos o constantes en variables que son empleados en otros lenguajes de programación como Java y que también podemos emplearlos en Kotlin de una manera muy similar que en otros lenguajes de programación como Java; aunque con otro sintaxis al momento de definir el mismo; recordemos que en Java debemos declarar nuestro tipo enumerado o enum de la siguiente forma:

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

Aunque normalmente los tipos enumerados o enum pueden tener algún tipo de datos o información contenida dentro de ellos; para ser algo así como los Pair en Kotlin que tratamos en una anterior entrada; en Java o la forma Java sería como la siguiente:

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

En Kotlin, como vimos en el caso de las Clases en Kotlin todo lo podemos llevar en su mínima expresión y con indicar el tipo y nombre del mismo es suficiente para crear un tipo enumerado o emun:

enum class NombreEnumerado

Pero el código anterior de esa forma nos sirve para muy poco para arreglar esto tenemos que abrir las llaves he indicamos todos los valores posible para nuestro enumerado; por ejemplo:

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

Como puedes ver, es un objeto en donde define cada una de sus constantes separadas por comas.

En estos casos, cuando accedemos a algunos de los valores definidos en el emun anterior; por ejemplo Planetas.URANO esto nos devolvería como salida el nombre del planeta, en este caso un texto en mayúscula que representa el nombre del planeta:

"URANO"

Inclusive podemos tener distintos tipos en las clases enumeradas, aunque sea un poco extraño seguro que se le podrá encontrar más de una utilidad:

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

Otro posible uso es un gestor de eventos en los que estás trabajando con distintos datos, por ejemplo, para tu modelo necesitarías tener:

  • Título: de tipo texto
  • Contenido: de tipo texto
  • Imagen: puede ser un tipo booleano
  • Fecha: un datetime podría funcionar bien

Entonces, si por alguna razón quisiéramos emplear un enum de Kotlin, tendríamos que manejar distintos tipos de datos, aunque para este ejemplo nos podría también funcionar bien los data class en Kotlin pero todo depende de como tengas conceptualizado tu aplicación; otro ejemplo sería una mensajería de Chat que sería similar al ejemplo anterior; todavía podemos potenciar esto más definiendo nuestros propios valores junto con los enum como veremos en la siguiente sección.

Definiendo valores constantes en los emun mediante un constructor

Cada uno de las definiciones de los tipos enumerados son valores constantes, así que para inicializarlos hay que indicar un valor luego de indicar el nombre del mismo entre paréntesis y definir un método constructor como hicimos anteriormente:

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

Cómo ves, la sintaxis sigue siendo muy limpia, con algunos agregados más y seguimos empleando el enum class como se realizó anteriormente, al ser un método constructor, podemos especificar tantos parámetros como queramos:

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

Aunque este paso (inicializar los valores) es opcional y en muchos otros casos, dependiendo del modelo que queremos emplear no tenemos que colocarlo; por ejemplo, si solo necesitamos una simple referencia como especificamos en un inicio o en el siguiente ejemplo:

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

O el mismo ejemplo que podemos encontrar en la web de Kotlin:

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

Como puedes ver, los tipos de datos enumerados o enum son muy útiles cuando queremos clasificar distintos modelos de datos, pero de los cuales no necesitamos realizar operaciones mediante métodos u otros elementos, tan solo una mera referencia y a lo más un valor que lo acompañe.

Usando los emuns en Kotlin desde nuestro código

Ahora, para poder emplear algunos de los enums que vimos anteriormente; por ejemplo, el de los planetas del sistema solar:

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)

Sobrescribiendo métodos en los emum

Podemos crear nuestros propios métodos e inclusive sobrescribir los ya existentes, por ejemplo el método toString() de la siguiente forma:

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

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

Definiendo métodos abstractos en los enums

Y esto sería todo con los tipos enumerados en Kotlin, puede tener más información en el siguiente enlace: Kotlin: Enum Classes

Podemos definir métodos static pero a la forma de Kotlin, con los companion dentro del mismo enum para que realice por ejemplo, una impresión de todos los valores de los enumerados:

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
}

O podemos extender las función sin necesidad de implementarla dentro del tipo enumerado:

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
}

Clases anónimas: Supertipos, sobrescribir y métodos abstractos en Kotlin:

Podemos definir en los enums valores contaste mediante funciones y clases anónimas como mostramos a continuación; son ideales para mostrar o realizar algún cálculo que tenga que ver con el resto de la estructura que realicemos en los tipos enumerables y esto es otra prueba de la versátil y consistente que resultan las estructuras de datos en 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
}

Clases enumeradas para implementar interfaces

Las interfaces son un tipo de mecanismo que tenemos en las clases como tratamos en una anterior entrada que hablamos sobre los tipos de las clases; las interfaces también están disponibles en los enumerados y podemos emplearlas de la siguiente forma:

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
}

Cómo puedes darte cuenta, declaramos una interfaz llamada Interes con un método la cual luego sobreescribimos en el enum que tiene el mismo nombre de la interfaz.

Iteración de los tipos de datos enumerados

De igual manera es muy fácil iterar nuestras constantes definidas en los enum, con indicar el enun como si de un array se tratase, luego la función values que nos devuelve la colección de los enum y tomamos el valor del enum com la propiedad name:

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

Como puedes ver, es esta forma podemos recorrer los enums como un array, esto es útil ya que muchas veces necesitamos procesar el enum como un todo, comparar sus valores de una forma directa y para eso es el método value; si quieres saber el tamaño:

 UserEnum.values().size

Si quieres buscar directamente dentro de la colección de enumerados, pasando como parámetro un string que haga match (o no) con la constante:

 UserEnum.valueOf("NAME")

Para saber la posición de una constante dentro de la colección de enumerados:

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

Enumerados dentro de otros enumerados

También podemos embeber enumerados dentro del otro, y podemos aplicar todo lo visto hasta ahora en cualquier nivel; por ejemplo, un enum dentro de otro enum:

enum class Meses {
    Enero,
    Febrero;

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

Conclusión

En esta entrada vimos lo potente y versátiles que son los enumerados en Kotlin, una buena guía que te permitirá realizar las operaciones más comunes con los enumerados en Kotlin; saber conceptos básicos e inicializar constantes mediante constructores, emplear métodos e interfaces, embeber enumerados, recorrerlos como si fueran una lista o un array, colocar los companion object y muchas otras operaciones más.

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.