1. when como herramienta de validacion
Para que sirve la validacion
Si alguna vez has introducido «edad = 999» o «nota = -12» y el programa se lo «ha tragado», ya conoces el dolor de no tener validacion. La validacion es la capa que separa «el programa acepta los datos» de «el programa se cree los datos». Y aqui when de repente se convierte no solo en una ramificacion, sino en una pequena tabla de reglas: que valores son aceptables, cuales no, y cuales requieren un mensaje aparte.
Imagina que necesitas comprobar un numero y asignarlo a una categoria: «0..12», «13..17», «18..120», y si no, «error». Si lo escribes con if, te saldra una cadena que se lee como un documento legal. Con when, en cambio, se lee como una escala limpia.
Rangos e in / !in
Un rango en Kotlin es un objeto del tipo a..b, que significa «todos los valores desde a hasta b, inclusive». La palabra «inclusive» es muy importante: 0..10 contiene tanto 0 como 10. Y el operador in es la comprobacion de «pertenece el valor al rango». En consecuencia, !in significa «no pertenece».
De momento suena a matematicas de colegio, pero en codigo se convierte en frases muy claras: age in 0..120 se lee literalmente como «la edad esta en el rango de 0 a 120». Y ahora lo importante: en when puedes escribir ramas no solo para valores concretos, sino tambien para comprobaciones de pertenencia a un rango.
Un pequeno calentamiento:
fun main() {
val x = 7
println(x in 1..10) // true
println(x !in 1..10) // false
}
Aqui no hay magia: in devuelve un Boolean, y con ese Boolean es comodo controlar la logica del programa.
Validacion de Int por rangos
Cuando un numero no solo hay que «aceptar/rechazar», sino clasificarlo por categorias, when con rangos resulta casi ideal. Escribes la escala de arriba abajo y el programa elige la primera rama que encaja. Para un principiante es agradable porque el codigo parece «reglas en papel»: primero ninos, luego adolescentes, luego adultos; si no, error.
Empecemos con una mini funcion que clasifica la edad. Fijate: esto es exactamente validacion + interpretacion: no solo decimos «mal/bien», devolvemos un resultado con sentido.
fun ageCategory(age: Int): String {
return when (age) {
in 0..12 -> "nino"
in 13..17 -> "adolescente"
in 18..120 -> "adulto"
else -> "edad no valida"
}
}
fun main() {
println(ageCategory(10)) // nino
println(ageCategory(17)) // adolescente
println(ageCategory(200)) // edad no valida
}
Aqui ves tres cosas importantes a la vez. Primero, in 0..12 es la «condicion de la rama» en when. Segundo, las ramas van de arriba abajo y se ejecuta la primera que coincide. Tercero, else es nuestra red de seguridad para «todo lo demas».
Proteccion temprana con !in
Hay un estilo de validacion muy practico: primero comprobamos que el numero tenga un minimo sentido, y solo despues lo dividimos en categorias. En when es comodo hacerlo con !in ... en la rama superior. Es como un guardia en la entrada: «con pasaporte, pasa; sin pasaporte, adios, y da igual como vistas».
Ejemplo con temperatura (el rango es orientativo, solo para ver la idea):
fun temperatureLabel(t: Int): String {
return when (t) {
!in -50..60 -> "fuera de un rango razonable"
in -50..0 -> "hace frio"
in 1..25 -> "normal"
else -> "hace calor"
}
}
fun main() {
println(temperatureLabel(10)) // normal
println(temperatureLabel(200)) // fuera de un rango razonable
}
Por que es comodo? Porque separas claramente «datos malos» de «datos buenos». Y luego no tienes que pensar donde «encajar» -999 o 999 en tu escala.
Rangos para Char
En tareas reales validaras no solo numeros, sino tambien el formato de entrada. Incluso antes de llegar a las expresiones regulares (que vendran mas adelante) puedes hacer muchas cosas utiles simplemente comprobando caracteres. Por ejemplo: «es un digito», «es una letra latina», «es mayuscula o minuscula». Y de repente resulta que los rangos tambien funcionan con Char.
Esto es especialmente util cuando tomas «el primer caracter de la respuesta» y esperas algo como Y/N, A/B/C o 1/2/3.
fun charKind(ch: Char): String {
return when (ch) {
in '0'..'9' -> "digito"
in 'a'..'z' -> "letra latina minuscula"
in 'A'..'Z' -> "letra latina mayuscula"
else -> "otro simbolo"
}
}
fun main() {
println(charKind('7')) // digito
println(charKind('Q')) // letra latina mayuscula
println(charKind('?')) // otro simbolo
}
Es importante entender que el rango '0'..'9' no es «magia de ASCII», sino simplemente el orden de los caracteres en Unicode, que esta organizado de forma que los digitos van seguidos. Para entradas de CLI de estudio y de practica, esto es mas que suficiente.
2. Agrupacion y combinacion de reglas
Agrupar valores con comas
A veces la validacion no son rangos, sino un conjunto de valores permitidos. Por ejemplo: «el comando solo puede ser 1, 2 o 3», «el mes puede ser 1..12», «estos meses tienen 31 dias, estos 30». Si escribes una rama por cada valor, enseguida te sentiras como una impresora que repite las mismas lineas.
En when puedes listar varios valores en una misma rama, separados por comas. No solo ahorra lineas: hace la regla mas obvia, «estos valores son un grupo».
Ejemplo (dias del mes, sin detalles de anios bisiestos; no te metas con febrero, bastante hace):
fun daysInMonth(month: Int): Int {
return when (month) {
1, 3, 5, 7, 8, 10, 12 -> 31
4, 6, 9, 11 -> 30
2 -> 28
else -> 0
}
}
fun main() {
println(daysInMonth(11)) // 30
println(daysInMonth(2)) // 28
println(daysInMonth(99)) // 0
}
Aqui else -> 0 tambien es una forma de validacion: devolvemos una «senal de error» con un numero. No es ideal (seria mejor un mensaje aparte), pero a este nivel del curso es una forma honesta y clara de mostrar: «el mes no es valido».
Rangos y valores concretos en un mismo when
A menudo la regla se ve asi: hay un rango general de valores aceptables, pero dentro hay «casos especiales». Por ejemplo: «puntuacion de 0 a 100, pero 100 merece una felicitacion aparte», o «descuento por edad, pero 0 es claramente un error de entrada».
La idea es que when te permite ordenar las ramas para que los casos particulares vayan arriba y los rangos amplios abajo. Y se lee casi como una prioridad de reglas: «si es exactamente 100, entonces esto; si no, si esta en 90..99, entonces esto...».
fun scoreComment(score: Int): String {
return when (score) {
!in 0..100 -> "resultado no valido"
100 -> "perfecto! (y si, puedes presumir un poco)"
in 90..99 -> "excelente"
in 70..89 -> "bien"
else -> "hay cosas que mejorar"
}
}
fun main() {
println(scoreComment(100)) // perfecto! (y si, puedes presumir un poco)
println(scoreComment(105)) // resultado no valido
}
Fijate en el orden. Si pusieramos in 90..100 mas arriba, 100 quedaria «absorbido» por esa rama y el mensaje especial para 100 no se ejecutaria nunca.
Mini escenario: ajustes de una aplicacion de consola
Ahora unamos todo en un pequeno fragmento completo. Imaginemos que hacemos una aplicacion de consola “Study Helper”, que pregunta al usuario el «nivel de dificultad» (1..3) y el «porcentaje de respuestas correctas» (0..100). Nuestra tarea es evitar que el programa acepte basura en silencio y, a la vez, dar mensajes claros.
No tocamos ni colecciones ni arquitectura compleja: solo readln(), trim(), toIntOrNull() y when.
fun difficultyLabel(level: Int): String {
return when (level) {
1 -> "facil"
2 -> "normal"
3 -> "dificil"
else -> "nivel no valido"
}
}
fun main() {
print("Nivel de dificultad (1-3): ")
val level = readln().trim().toIntOrNull()
val message = when (level) {
null -> "No es un numero"
in 1..3 -> "Ok, elegido: ${difficultyLabel(level)}"
else -> "Hay numero, pero debe ser 1..3"
}
println(message)
}
Fijate en la «escalera» tranquila. Primero comprobamos null (no se pudo parsear), luego comprobamos el rango y solo despues todo lo demas. Este orden casi siempre da el mensaje mas humano: el usuario entiende exactamente que hizo mal.
Solapamiento de rangos y orden de las ramas
when comprueba las ramas de arriba abajo y toma la primera que encaja. Suena simple, pero justo aqui nace uno de los bugs mas traicioneros del principiante: «escribi todas las reglas bien, pero el programa se comporta mal». Normalmente significa que las reglas se solapan, y una rama mas amplia esta por encima de otra mas estrecha.
Veamos un ejemplo donde el error casi no se nota:
fun badGrade(score: Int): String {
return when (score) {
in 0..100 -> "algun resultado"
in 90..100 -> "excelente"
else -> "error"
}
}
fun main() {
println(badGrade(95)) // algun resultado
}
Por que pasa? Porque 95 ya entro en 0..100, y el programa ni siquiera llego a la rama 90..100.
La variante correcta es reordenar las ramas:
fun goodGrade(score: Int): String {
return when (score) {
in 90..100 -> "excelente"
in 0..89 -> "algun resultado"
else -> "error"
}
}
fun main() {
println(goodGrade(95)) // excelente
}
Si solo recuerdas una idea de esta leccion, que sea esta: en when el orden de las ramas es parte de la logica, no solo «un formato bonito».
3. Chuletas y visualizacion
Tabla-chuleta de validacion en when
Cuando escribes validacion, es util tener un par de «formulas» en la cabeza. Abajo tienes una pequena chuleta con las construcciones mas habituales. No sustituye la comprension, pero ahorra tiempo cuando por centesima vez escribes la comprobacion «0..100».
| Que queremos comprobar | Como lo escribimos en when (x) | Como se lee |
|---|---|---|
| Pertenencia a un rango | |
«x de 0 a 100» |
| No pertenece a un rango | |
«x no esta entre 0 y 100» |
| Varios valores concretos | |
«x es 1 o 3 o 5» |
| Un «caso especial» | |
«si es exactamente 100, logica especial (poner por encima del rango)» |
| Todo lo demas | |
«cualquier otro valor» |
Observa que Kotlin fomenta el uso de when precisamente como expresion, cuando quieres obtener un resultado y no solo imprimir en cada rama. Esto ayuda tanto a la legibilidad como a la disciplina del codigo.
Diagrama de flujo del canal de validacion
Para no convertir la entrada en caos, es comodo pensar la validacion como un pequeno canal. Ya lo hemos hecho muchas veces a mano, pero ahora podemos fijarlo visualmente: pasamos de una cadena cruda a un valor correcto o a un error comprensible.
flowchart TD
A["readln(): cadena cruda"] --> B["trim(): quitamos espacios"]
B --> C["toIntOrNull(): numero o null"]
C --> D{"when (value)"}
D -->|null| E["Mensaje: 'No es un numero'"]
D -->|in rango| F["Ok: seguimos trabajando"]
D -->|else| G["Mensaje: 'Hay numero, pero no encaja'"]
La idea es simple: when se convierte en el ultimo paso, donde transformas «solo un valor» en «un valor valido o una reaccion comprensible».
4. Errores tipicos al validar con when
Error n.º 1: rangos solapados, donde la rama amplia esta por encima de la estrecha.
Es el caso clasico: estas seguro de que «lo describiste todo», pero parte de la logica nunca se ejecuta. Si tienes una rama in 0..100, entonces todos los rangos mas estrechos dentro de ella deben ir por encima; si no, se vuelven inalcanzables en la practica.
Error n.º 2: un limite olvidado por la “inclusividad” de ...
Los principiantes a menudo piensan el rango como «hasta, pero sin incluir», sobre todo si antes vieron rangos asi en otros lenguajes o en notacion matematica. En Kotlin 0..10 incluye 10, por eso los errores «de 1» son frecuentes. La cura es simple: di en voz alta los valores extremos y compruebalos mentalmente por separado.
Error n.º 3: falta de un else con sentido o intentar vivir sin el.
En validacion, else no es «para que el compilador deje de molestar», sino la rama obligatoria de «que hacer con valores incorrectos». Si else devuelve algo absurdo como una cadena vacia, estas ocultando el error en lugar de comunicarlo. Luego te explotara en el momento menos esperado.
Error n.º 4: mezclar en una misma rama calculos, impresion y cambio de estado.
Cuando when se usa para validar, es especialmente bueno como expresion: devuelves mensaje/estado/numero y la impresion la haces aparte. Si en las ramas empieza «imprimir, cambiar una variable y volver a leer la entrada», tu when se convierte en una miniserie de 12 temporadas que es dificil de testear y leer.
Error n.º 5: no comprobar null despues de toIntOrNull().
Si has parseado la cadena con toIntOrNull(), entonces null es un resultado normal, no una excepcion. A menudo la estructura mas clara es una rama separada null -> "No es un numero", y solo despues los rangos. Asi el usuario recibe una explicacion precisa y tu no peleas con el compilador y los tipos nullable.
GO TO FULL VERSION