L3: Jerarquías y Control de Flujo Exhaustivo
El Infierno del switch de Java
Sección titulada «El Infierno del switch de Java»Si vienes de Java, C o JavaScript, seguramente estás familiarizado con la sentencia switch. Históricamente, el switch ha sido una de las estructuras de control más propensas a errores. ¿Por qué? Por culpa del Fall-through.
En esos lenguajes, si olvidas escribir la palabra clave break; al final de un caso, el programa continuará ejecutando el siguiente caso hacia abajo, causando bugs lógicos gravísimos y muy difíciles de rastrear.
Kotlin decidió acabar con este problema de raíz eliminando por completo la palabra switch e introduciendo una estructura inmensamente más poderosa y segura: when.
La Evolución: if y when como Expresiones
Sección titulada «La Evolución: if y when como Expresiones»En Kotlin, estructuras como if y when no son simples “órdenes” (statements), sino Expresiones (expressions). Esto significa que pueden devolver un valor.
En Java, si querías asignar un valor dependiendo de una condición, tenías que crear una variable mutable y modificarla dentro del if o del switch. En Kotlin, puedes asignar el resultado de un when directamente a una constante (val).
// Asignamos el resultado de toda la evaluación 'when' directamente a la variable.// No hay necesidad de crear variables 'var' mutables intermedias.val selectedOption: MenuOption = when (rawOption) { "1" -> MenuOption.CHAT "2" -> MenuOption.SETTINGS "3" -> MenuOption.EXIT else -> MenuOption.UNKNOWN}Bajo el Cofre: El Bytecode de when
Sección titulada «Bajo el Cofre: El Bytecode de when»¿Cómo funciona when en las entrañas de la JVM (Java Virtual Machine)? El compilador de Kotlin es muy inteligente y optimiza la traducción a bytecode de Java dependiendo de cómo uses el when:
- Pocas Ramas: Si tu
whensolo tiene 2 o 3 opciones, el compilador lo traduce a una simple cadena de instruccionesif-else if. - Múltiples Ramas o Enums: Si tu
whentiene muchos casos, o si estás evaluando unenum class, el compilador activa el modo turbo y genera instrucciones de Java Bytecode optimizadas:TABLESWITCH: Se usa si las claves son contiguas. Ofrece un acceso en memoria de tiempo constante ($O(1)$).LOOKUPSWITCH: Se usa si las claves están dispersas. Realiza una búsqueda binaria garantizando un tiempo de acceso de $O(\log n)$.
🧠 Puente Conceptual: Control de Flujo y Pattern Matching en tu Lenguaje
Sección titulada «🧠 Puente Conceptual: Control de Flujo y Pattern Matching en tu Lenguaje»- En JS/TS clásico, la sentencia
switchsufre del peligroso fall-through si olvidasbreak;, y no es una expresión (no devuelve un valor que puedas asignar directamente a una variable). - Para simular pattern matching en TypeScript, se utilizan uniones discriminadas (Discriminated Unions como
type Action = { type: 'chat' } | { type: 'exit' }) evaluadas en unswitch(action.type)o librerías externas comots-pattern. - En Kotlin,
whensoporta expresiones nativas, descarta elbreakpor diseño y realiza Smart Casts automáticos dentro de cada rama sin necesidad de librerías externas.
- A partir de Python 3.10, se introdujo el Structural Pattern Matching con la sentencia
match / case. - La sintaxis de
whenen Kotlin inspiró fuertemente esta evolución: en Pythoncase "1":equivale a"1" ->, y el comodíncase _:equivale aelse ->. - Sin embargo, en Python
matchsigue siendo una declaración (statement) que no devuelve un valor asignable directamente, mientras que en Kotlinval opcion = when (...)es un patrón idiomático fundamental.
- A partir de C# 8.0, C# introdujo las
switch expressions(var result = raw switch { "1" => MenuOption.Chat, _ => MenuOption.Unknown };). - El
whende Kotlin y lasswitch expressionsde C# comparten la misma filosofía: ambos son expresiones de primera clase, descartan la necesidad debreak;, y exigen exhaustividad cuando se evalúan sobre jerarquías cerradas o enums.
- Java tradicional solo contaba con la sentencia
switchimperativa con fall-through obligatorio. - A partir de Java 14/17+, Java incorporó las Switch Expressions con sintaxis de flecha (
case "1" -> MenuOption.CHAT;), y en Java 21 el pattern matching formal para tipos y registros. - Kotlin diseñó
whencomo expresión desde su versión 1.0 (2016), convirtiéndolo en un pilar idiomático del lenguaje desde sus orígenes.
Bucles y Estados Interactivos
Sección titulada «Bucles y Estados Interactivos»Antes de saltar al laboratorio, necesitamos una herramienta más: el bucle while.
Dado que en Kotlin el clásico bucle for se reserva casi exclusivamente para recorrer colecciones o listas (que veremos en la Lección 7), cuando necesitamos mantener una aplicación viva de forma interactiva (como el Game Loop de un videojuego o el menú de nuestra CLI), usamos un bucle while.
// Mantiene la aplicación ejecutándose en un ciclo infinito// hasta que explícitamente usemos la palabra reservada 'break'.while (true) { // 1. Mostrar menú // 2. Leer input // 3. Evaluar input con 'when'}El Superpoder: Exhaustividad (Certeza del Compilador)
Sección titulada «El Superpoder: Exhaustividad (Certeza del Compilador)»La verdadera magia de Kotlin no es solo que when sea bonito, sino su concepto de Control de Flujo Exhaustivo.
Cuando utilizas when como una expresión (es decir, devolviendo un valor) o cuando evalúas una “jerarquía cerrada”, el compilador te obliga matemáticamente a manejar TODAS las posibilidades. Si se te olvida un caso, el código simplemente no compilará.
Para sacarle jugo a este superpoder, Kotlin nos ofrece las Jerarquías Cerradas:
1. Clases Enumeradas (enum class)
Sección titulada «1. Clases Enumeradas (enum class)»Se utilizan para representar un conjunto fijo y constante de valores. Piensa en ellos como un interruptor de opciones absolutas (ej. Días de la semana, Estados de un pedido, Opciones de menú).
// Definimos un conjunto estricto de opciones válidas para nuestra aplicación.enum class MenuOption { CHAT, SETTINGS, EXIT, UNKNOWN}Si le pasamos un MenuOption a un when, Kotlin nos exigirá escribir una rama para CHAT, SETTINGS, EXIT y UNKNOWN. Si el día de mañana agregas la opción HELP al enum, ¡el compilador romperá tu código en todos los when donde olvidaste manejar HELP! Esto hace que refactorizar sea increíblemente seguro.
2. Clases Selladas (sealed class y sealed interface)
Sección titulada «2. Clases Selladas (sealed class y sealed interface)»Son como los Enums, pero con esteroides. Mientras que un enum solo tiene valores constantes (como botones estáticos), una clase sellada permite que cada opción tenga su propio estado y datos.
Por ejemplo, un estado de red podría ser Error(val exception: Exception) o Success(val data: String). Profundizaremos en ellas en la Lección 10: Companion Objects and Object Declarations.
Laboratorio Incremental: Menú Principal y while
Sección titulada «Laboratorio Incremental: Menú Principal y while»Hoy llevaremos nuestro Chat CLI al siguiente nivel. Envolveremos el saludo de la Lección 2 dentro de un bucle infinito y presentaremos un menú interactivo. Parsearemos la entrada de texto cruda hacia nuestro propio enum class para garantizar un control de flujo exhaustivo.
Entregables del Laboratorio
Sección titulada «Entregables del Laboratorio»Instrucciones Paso a Paso
Sección titulada «Instrucciones Paso a Paso»Nota: A partir de esta lección, debes escribir todo tu código fuente, variables y enums en Inglés.
Paso 1: Definir la jerarquía de opciones
Sección titulada «Paso 1: Definir la jerarquía de opciones»Hasta arriba de tu archivo Main.kt (fuera de la función main), declara un enum para restringir matemáticamente las opciones de nuestro menú.
// Definimos un Enum para representar las opciones de nuestro menú de forma estricta.enum class MenuOption { CHAT, SETTINGS, EXIT, UNKNOWN}Paso 2: Crear el bucle infinito
Sección titulada «Paso 2: Crear el bucle infinito»Justo debajo de donde imprimimos Hello, $username!, abre un bloque while (true) e imprime las opciones del menú.
// Bucle principal (Main Loop) que mantiene viva la aplicación interactiva while (true) { println("\n--- Main Menu ---") println("1. Chat") println("2. Settings") println("3. Exit") print("Choose an option: ")
val rawOption: String? = readlnOrNull()
// Continuaremos en el Paso 3 aquí adentro... }Paso 3: Parseo seguro usando when como expresión
Sección titulada «Paso 3: Parseo seguro usando when como expresión»Adentro del while, debajo del readlnOrNull(), usaremos un when para convertir la cadena de texto cruda "1" en nuestra constante segura MenuOption.CHAT. Fíjate cómo asignamos el resultado directamente a val selectedOption.
// Usamos 'when' como expresión para mapear el input crudo (String) // a nuestro Enum estructurado, devolviendo el valor directamente. val selectedOption: MenuOption = when (rawOption) { "1" -> MenuOption.CHAT "2" -> MenuOption.SETTINGS "3" -> MenuOption.EXIT else -> MenuOption.UNKNOWN }Paso 4: Ejecución Exhaustiva
Sección titulada «Paso 4: Ejecución Exhaustiva»Ahora usaremos nuestro Enum estructurado para ejecutar la lógica de la aplicación. Escribe este segundo when debajo del anterior:
// Usamos 'when' nuevamente, pero esta vez con nuestro Enum. Al ser una jerarquía // cerrada (el compilador conoce todas las opciones), nos obliga a manejar // exhaustivamente todos los casos posibles sin necesidad de un 'else'. when (selectedOption) { MenuOption.CHAT -> { println(">>> Entering chat mode... (Work in progress)") } MenuOption.SETTINGS -> { println(">>> Entering settings... (Work in progress)") } MenuOption.EXIT -> { println("Goodbye, $username! See you soon.") break // Rompe el bucle while(true) y finaliza el programa } MenuOption.UNKNOWN -> { println("Invalid option. Please try again.") } }Paso 5: ¡Ejecuta y prueba tu menú en tu entorno preferido!
Sección titulada «Paso 5: ¡Ejecuta y prueba tu menú en tu entorno preferido!»Puedes probar tu menú interactivo según la herramienta que estés utilizando:
- En Android Studio / IntelliJ IDEA: Presiona el botón verde de Play (▶️) junto a
fun main()y escribe en la pestaña inferior Run. - En Google Antigravity / Visual Studio Code: Presiona el atajo
Cmd + Shift + B(macOS) oCtrl + Shift + B(Windows/Linux) para abrir la terminal interactiva integrada. - En Terminal de comandos: Ejecuta:
(En Windows:
Ventana de terminal ./gradlew run --console=plain -qgradlew.bat run --console=plain -q)
Casos de prueba a verificar:
- Después de ingresar tu nombre, el menú debe desplegarse con las 3 opciones.
- Escribe
1y presiona Enter: debe imprimir el mensaje de chat y volver a presentarte el menú (gracias a la persistencia del buclewhile). - Escribe una opción inexistente (por ejemplo
"9"o"hola"): el caso exhaustivoMenuOption.UNKNOWNatrapará el error e imprimirá"Invalid option. Please try again.". - Escribe
3: el comandobreakromperá el buclewhile (true)y finalizará el programa despidiéndose con tu nombre.
¡Felicidades! Has construido la columna vertebral interactiva de nuestra aplicación usando los principios de control de flujo exhaustivo.
Fuentes y Lecturas Recomendadas
Sección titulada «Fuentes y Lecturas Recomendadas»Feedback y Dudas de la Lección
Sección titulada «Feedback y Dudas de la Lección»¿Tienes alguna duda sobre when exhaustivo, el bytecode de la JVM o el bucle del menú?