2. Selección: if / else if / else
UD3 — Control de Flujo y Depuración · RA3 — Escribe y depura código, analizando y utilizando las estructuras de control del lenguaje
IES Thiar · Curso 2026 - 2027
| Aspecto | Valor |
|---|---|
| Resultado de aprendizaje | RA: 3 — Escribe y depura código, analizando y utilizando las estructuras de control del lenguaje. |
| Criterios de evaluación a los que contribuye esta sección | CE 3.1 (escribe y prueba código que hace uso de estructuras de selección) CE 3.5 (crea programas ejecutables combinando selección con lectura de datos) CE 3.7 (comenta y documenta el código). |
| Marco normativo | RD 405/2023 · RD 450/2010 · RD 659/2023 |
| Tecnología base | Java 25 LTS (OpenJDK Temurin 25) — soporte hasta 2030+ |
| Horas estimadas | ~5 h (de las 26 h de la UD3). |
Requisitos previos (lo que ya sabes): condiciones booleanas, operadores relacionales y lógicos, paréntesis, De Morgan, variables booleanas con nombre y diagramas de flujo (todo de la sección 1 de la UD3); tipos primitivos,
var,String(con.equals()y.length()),text blocksyfinalpara constantes (de UD1); entrada conjava.lang.IO(IO.readln,IO.println) yjava.util.Scanner(nextInt,nextDouble,nextLine), conversión conInteger.parseInt/Double.parseDouble(de UD1). El operador ternario es contenido nuevo de esta sección, no un repaso.
2.1 ¿De qué va esta sección? — Por fin escribimos if
Bienvenido a la sección donde por fin pasa lo que llevas esperando desde que terminaste la sección 1: vamos a escribir if en Java. Después de todo el discurso teórico sobre los tres pilares y los diagramas de flujo, toca vestirlos con sintaxis.
Te prometo algo: si la sección 1 te pareció "filosofía", esta sección te va a parecer "oficina". Aquí es donde el programa empieza a comportarse como una aplicación de verdad, a pedir datos, a comprobarlos y a decidir qué hacer en función de ellos. Vas a escribir código que reacciona, no que solo "hace".
Vamos a cubrir, en este orden:
- La sintaxis básica del
if: la forma simple, la forma conelsey la forma encadenada conelse if. - La anatomía detallada del
if: la condición, el bloque, las llaves y por qué siempre las usamos. - Los
ifanidados y la técnica del early return (cláusulas de guarda), que es lo que separa a un principiante de un profesional. - El operador ternario (contenido nuevo): qué es, en su sitio natural, cuándo vale la pena usarlo y cuándo es mejor un
if. - Los problemas más habituales del
if, incluido el famoso dangling else y el bug de Apple "goto fail". - La validación de entrada con
Scanneryif: cómo pedir datos al usuario sin que el programa se vuelva loco. - Varios ejemplos resueltos paso a paso, porque la teoría sin práctica es como un mapa sin viaje.
Analogía del agente de la estación
Piensa en el if como el agente de la estación de tren que mira tu billete. Si el billete es de primera, te manda al andén 1. Si es de segunda, al andén 2. Si no tienes billete, te manda a taquilla. El agente no te manda a los tres sitios a la vez: examina una condición, decide una rama, y descarta el resto. Eso es exactamente lo que hace Java con if / else if / else.
Vamos a empezar por el principio. Respira hondo, abre IntelliJ, crea una clase PruebasIf en tu proyecto Maven, y acompáñame línea por línea.
2.2 La sintaxis básica del if
El if es la estructura de selección más utilizada en cualquier lenguaje. En Java tiene tres formas básicas: simple, con alternativa y encadenada. Vamos a verlas una a una, con su sintaxis exacta, su diagrama de flujo y un ejemplo real en cada caso.
2.2.1 Forma 1: if simple
La forma más simple del if ejecuta un bloque de código solo si la condición se evalúa a true. Si la condición es false, el bloque se salta y el programa continúa por donde iba.
Sintaxis:
Ejemplo:
Si temperatura vale 38, la salida es:
Si temperatura vale 36, la salida es solo:
Fíjate en dos cosas clave:
- La condición va siempre entre paréntesis:
if (temperatura > 37). Sin paréntesis no compila. - El bloque va entre llaves
{ }. Aunque Java te permite omitirlas cuando el bloque tiene una sola sentencia, nosotros siempre las pondremos. Más adelante verás por qué.
Diagrama de flujo:
flowchart TD
Inicio([Inicio]) --> Condicion{"temperatura > 37 ?"}
Condicion -->|Sí| Aviso["IO.println 'Atención: fiebre'"]
Condicion -->|No| Skip["(salta el bloque)"]
Aviso --> Fin([Fin])
Skip --> Fin
La rama "No" no hace nada: simplemente se salta el bloque y continúa. Esa es la gracia del if simple.
2.2.2 Forma 2: if con else
Cuando quieres hacer una cosa si la condición es cierta y otra distinta si es falsa, añades un else. Garantizado: una de las dos ramas se va a ejecutar, nunca las dos, nunca ninguna.
Sintaxis:
Ejemplo:
Salida con edad = 17:
Diagrama de flujo:
flowchart TD
Inicio([Inicio]) --> Condicion{"edad >= 18 ?"}
Condicion -->|Sí| Mayor["IO.println 'Mayor de edad'"]
Condicion -->|No| Menor["IO.println 'Menor de edad'"]
Mayor --> Fin([Fin])
Menor --> Fin
Ese rombo con dos salidas es la imagen mental que debes tener siempre cuando veas un if. Si dibujas el flujo en papel antes de programar, no te equivocarás nunca.
2.2.3 Forma 3: if encadenado con else if
Cuando tienes más de dos caminos posibles, encadenas con else if. Java evalúa las condiciones en orden, una detrás de otra, y ejecuta solo el primer bloque cuya condición sea true. Una vez entra en uno, ignora todos los demás. Si ninguna condición se cumple, ejecuta el else final (si lo hay).
Sintaxis:
Ejemplo: clasificar una nota académica
Salida con nota = 7:
Detalles que te van a ahorrar problemas:
- Las condiciones se evalúan en orden. En cuanto una es
true, se ejecuta su bloque y se saltan todas las demás. Por eso no hace falta escribirnota >= 5 && nota < 7en la segunda rama: si llegamos hasta ahí, ya sabemos quenota < 5erafalse, es decir,nota >= 5ya es cierto. - El
elsefinal es opcional. Si no lo pones y ninguna condición se cumple, simplemente no se ejecuta ninguna rama. - Puedes poner tantos
else ifcomo quieras. Pero si llegas a seis o siete, probablemente lo que necesitas es unswitch(lo verás en la sección 3).
Diagrama de flujo de la clasificación de nota:
flowchart TD
Inicio([Inicio]) --> C0{"nota < 0 o nota > 10 ?"}
C0 -->|Sí| Invalid["IO.println 'Nota no válida'"]
C0 -->|No| C1{"nota < 5 ?"}
C1 -->|Sí| Susp["IO.println 'Suspenso'"]
C1 -->|No| C2{"nota < 7 ?"}
C2 -->|Sí| Aprob["IO.println 'Aprobado'"]
C2 -->|No| C3{"nota < 9 ?"}
C3 -->|Sí| Not["IO.println 'Notable'"]
C3 -->|No| Sobre["IO.println 'Sobresaliente'"]
Invalid --> Fin([Fin])
Susp --> Fin
Aprob --> Fin
Not --> Fin
Sobre --> Fin
Fíjate en la forma de "escalera": cada condición es una nueva decisión que solo se evalúa si todas las anteriores han sido false. Esa es la imagen mental del else if.
2.2.4 Comparativa de las tres formas
Antes de seguir, te dejo una tabla que resume las tres formas. Si solo te aprendes una tabla, que sea esta:
| Forma | ¿Cuándo se usa? | ¿Cuántas ramas? | ¿Tiene else? |
|---|---|---|---|
if simple |
Cuando una acción es opcional. | 1 (la del if). |
No. |
if con else |
Cuando hay exactamente dos caminos alternativos. | 2 (una en if, otra en else). |
Sí, obligatorio. |
if con else if y else final |
Cuando hay tres o más caminos mutuamente excluyentes. | N + 1 (N else if más el else final opcional). |
Sí, opcional al final. |
Regla mnemotécnica del profesor:
- 1 camino posible: no hace falta
if, es secuencia pura. - 1 camino opcional:
ifsimple (sinelse). - 2 caminos:
if+else. - 3+ caminos:
if+else if+ ... +elsefinal.
Cuando llegues a más de 5 ramas, párate y piensa si lo que necesitas es un switch (sección 3) o, directamente, una tabla de datos (UD6).
2.3 Anatomía detallada del if
Ahora que ya has visto las tres formas por encima, vamos a despiezar el if pieza por pieza. Entender cada parte por separado te va a permitir leer cualquier if ajeno y escribir los tuyos sin errores.
2.3.1 La condición: siempre entre paréntesis, siempre booleana
La condición es la expresión que va entre los paréntesis, justo después de la palabra if. Java exige dos cosas:
- Tiene que ir entre paréntesis. Sin excepciones.
if edad >= 18no compila. - Tiene que evaluarse a
boolean. Cualquier expresión que devuelvatrueofalsevale; cualquier otra cosa, no.
Ejemplos válidos:
Ejemplos que no compilan:
Comparar boolean con == true es redundante
Si esValido ya es boolean, escribir if (esValido == true) es redundante y feo. Lo correcto es if (esValido). Igualmente, en vez de if (esValido == false), escribe if (!esValido). Es más limpio y se lee como prosa.
2.3.2 El bloque: entre llaves
Después de la condición viene el bloque que se ejecuta si la condición es true. Java admite dos variantes:
Variante 1: con llaves (la que usaremos siempre)
Variante 2: sin llaves (legal, pero evítala)
La variante sin llaves solo permite una sentencia. Si más tarde añades una segunda sentencia, la indentación te engaña y se rompe:
Si edad = 16, la salida sería:
¿Raro, no? Pues es exactamente lo que hace Java: solo la primera sentencia pertenece al if. La segunda se ejecuta siempre. Para evitar este bug, siempre llaves, aunque el bloque tenga una sola línea.
2.3.3 La regla de las llaves obligatorias
Esta es una de las pocas reglas absolutas que adoptaremos en el curso:
REGLA: En este curso, todos los bloques de
if,else ifyelsevan entre llaves, aunque tengan una sola sentencia. Sin excepciones.
¿Por qué tan tajantes? Porque el famoso bug "goto fail" de Apple en 2014, que dejó vulnerable el SSL/TLS de iOS y macOS durante meses, fue causado exactamente por esto: una segunda línea dentro de un if sin llaves que se ejecutaba siempre.
Ese segundo goto fail estaba indentado como si estuviera dentro del if, pero no lo estaba, porque el if no tenía llaves. El compilador no se quejó, los tests no lo detectaron, y millones de dispositivos estuvieron meses con el handshake SSL roto. Una línea sin llaves → vulnerabilidad de seguridad en producción.
La regla no es caprichosa: es experiencia amarga del mundo real. Acostúmbrate a poner llaves siempre y nunca tendrás ese problema.
2.3.4 El else y el else if pertenecen al mismo "if statement"
Una cosa que confunde al principio: aunque veas if / else if / else en líneas separadas, son parte de la misma estructura. No son tres if distintos, son una sola sentencia de control. Esto importa por dos razones:
- Indentación correcta: el
else ify elelsese alinean con elif, no se indentan. - Semántica: el
elsesolo se evalúa si la condición deliffuefalse. No se ejecutan ambos.
IntelliJ te avisa del mal indentado con una línea roja. Acepta siempre su sugerencia de "Reformat code" (Ctrl+Alt+L en Windows/Linux, Cmd+Alt+L en Mac).
2.3.5 Anatomía visual
Para que no se te olvide, aquí tienes la "radiografía" de un if completo:
flowchart TD
A["if (condicion1) { ... }"] --> B["¿condicion1 true?"]
B -->|Sí| R1["Bloque 1"]
B -->|No| C["else if (condicion2) { ... }"]
C --> D["¿condicion2 true?"]
D -->|Sí| R2["Bloque 2"]
D -->|No| E["else { ... }"]
E --> R3["Bloque else"]
R1 --> Fin(["Continúa el programa"])
R2 --> Fin
R3 --> Fin
A partir de aquí, todos los if que veas en cualquier libro o proyecto los vas a leer como un rombo con flechas. Ese es el nivel de abstracción que queremos que consigas.
2.4 if anidados y la técnica del early return
2.4.1 ¿Qué es anidar un if?
Anidar un if es meter un if dentro del bloque de otro if. Es perfectamente legal y, a veces, necesario. Por ejemplo, para validar que la edad está en rango y luego comprobar si tiene carnet:
Aquí el if (tieneCarnet) solo se evalúa si edad >= 18 ya es true. Es una estructura muy común: una validación dentro de otra.
2.4.2 El problema: la pirámide de la muerte
Anidar if dentro de if dentro de if produce lo que en la industria llamamos la pirámide de la muerte (o arrow anti-pattern, porque el código tiene forma de flecha apuntando a la derecha). Y es tan feo como suena:
Ese código es ilegible. Cuando intentes depurarlo, te vas a perder. Cuando llegue otro programador a tocarlo, te va a maldecir. Y, sin embargo, la lógica que expresa es razonable: validar varias cosas antes de hacer una operación. El problema no es la lógica, es la forma.
2.4.3 La técnica del early return (cláusulas de guarda)
La solución profesional a la pirámide de la muerte se llama early return ("retorno anticipado") o cláusulas de guarda ("guard clauses"). La idea es invertir las condiciones y salir cuanto antes.
Aviso pedagógico: vas a usar return antes de estudiarlo a fondo
En el código de abajo aparece la palabra clave return, que todavía no hemos estudiado formalmente. No es un error ni una contradicción: es un adelanto deliberado, porque la técnica de early return es tan útil que merece la pena conocerla ya, aunque sea con una explicación mínima apoyada en lo que ya sabes.
Lo que necesitas por ahora:
-
Desde UD1 sabes que todo programa Java necesita un método
main: es el único método que has escrito hasta ahora, y es el que la JVM ejecuta al arrancar el programa. -
Lo único nuevo aquí es que
return;hace que el método actual termine inmediatamente en ese punto. Comomaines el único método que existe en tus programas por ahora,return;dentro demainequivale a "el programa termina aquí".
return se estudiará formalmente como sentencia de salto en la sección 7 de esta misma unidad (junto a break y continue), y en profundidad —con métodos propios, parámetros y tipos de retorno— en UD4. Hasta entonces, con la idea de "termina el método actual (por ahora, siempre main) en este punto" te basta para aplicar correctamente esta técnica.
¿Ves la diferencia? En vez de ir anidando if para comprobar lo positivo, comprobamos lo negativo y salimos inmediatamente si no se cumple. El código queda plano, sin indentación creciente, y se lee como una lista de comprobaciones: "si esto falla, salgo; si no, sigo".
2.4.4 ¿Por qué funciona el early return?
El early return aprovecha la característica de return que acabas de ver: abandona el método inmediatamente (en nuestros programas, main), devolviendo el control a quien lo invocó —en el caso de main, a la JVM—. Volverás sobre esto con más detalle en la sección 7 (return como sentencia de salto) y en UD4 (métodos propios y su valor de retorno). Por eso, después de cada if negativo con return, no necesitas else: si llegamos a la siguiente línea, es porque la condición anterior no se cumplió.
Mentalmente se lee así:
"Si los datos no son válidos, error y me voy. Si el usuario no existe, error y me voy. Si no tiene permisos, error y me voy. Si está sancionado, error y me voy. Si no tiene saldo, error y me voy. Si he llegado hasta aquí, todo ha ido bien y hago la operación."
Es exactamente como piensa un cocinero cuando revisa la nevera antes de empezar a cocinar: si falta un ingrediente clave, cierra la nevera y no sigue.
2.4.5 Cuándo anidar y cuándo usar early return
| Situación | Recomendación |
|---|---|
| Validaciones previas a una operación (caso de error → salir). | Early return. |
| Lógica de negocio con varias ramas mutuamente excluyentes (suspenso/aprobado/notable). | else if (sin anidar). |
Comprobaciones que dependen una de otra (solo me importa tieneCarnet si ya es mayor de edad). |
if anidado, máximo 2 niveles. |
| Más de 3 niveles de anidamiento. | Refactoriza: extrae métodos o usa early return. |
Regla del profesor:
Si tu
ifanidado llega a tres niveles de indentación, paras y reescribes. Tres niveles es el límite donde el cerebro humano empieza a perderse.
2.4.6 Ejemplo guiado: refactorización paso a paso
Vamos a transformar un código con pirámide en uno con early return. Partimos de:
Paso 1: invertir las condiciones de los else (lo que era negativo pasa a positivo, y viceversa):
Paso 2: quitar los else porque el return ya ha salido (recuerda: por ahora usamos return dentro de main para terminar el programa; lo verás formalmente en la sección 7 de esta unidad y en profundidad en UD4):
Fíjate: la indentación ya no crece. Cada condición está alineada a la izquierda. Se lee de arriba a abajo como una lista. La legibilidad mejora radicalmente.
El return en main
En Java 25 puedes usar return; dentro de main para salir del programa (en void main() no devuelve nada, simplemente termina). Basta con que sepas que return; abandona el método actual; como main es, de momento, el único método que escribes, eso equivale a terminar el programa. Verás return formalmente como sentencia de salto en la sección 7 de esta unidad, y en profundidad —con métodos propios— en UD4.
2.5 El operador ternario en su sitio: cuándo sí y cuándo no
2.5.1 Una herramienta nueva: el operador ternario ? :
Hasta ahora en esta unidad has resuelto todas las decisiones con if / else if / else. Vamos a añadir una herramienta más a tu caja: el operador ternario ? :.
Lo que lo hace distinto de todo lo visto hasta ahora es que es una expresión (produce un valor que puedes asignar o pasar como argumento), no una sentencia (que ejecuta instrucciones). Su sintaxis:
Se lee así: "si condicion es true, el resultado es valorSiTrue; si es false, el resultado es valorSiFalse". Fíjate en el paralelismo con lo que ya conoces (dos fragmentos independientes, cada uno resuelve el mismo problema a su manera):
Ambos fragmentos hacen exactamente lo mismo: una línea en vez de cinco. El operador ternario no es una estructura de control distinta del if/else: es una forma más compacta de expresar un caso particular muy habitual, el de dos ramas que solo producen un valor cada una.
2.5.2 El operador ternario es if/else que produce un valor
La diferencia fundamental con if/else es:
| Característica | if / else |
Operador ternario ?: |
|---|---|---|
| ¿Es una sentencia o una expresión? | Sentencia: ejecuta instrucciones. | Expresión: produce un valor. |
| ¿Puede contener varias instrucciones? | Sí. | No: solo una expresión por rama. |
| ¿Se puede asignar a una variable? | No directamente (hay que asignar dentro de cada rama). | Sí: var x = cond ? a : b;. |
| ¿Puede ir como argumento a un método? | No. | Sí: IO.println(cond ? "Sí" : "No");. |
¿Admite else if encadenados? |
Sí. | Solo dos ramas (true/false). |
La clave
El ternario es para elegir entre dos valores, no para ejecutar bloques de código.
2.5.3 Cuándo usar el operador ternario (casos buenos)
El operador ternario es excelente cuando:
- Quieres asignar una variable en función de una condición simple.
- Tienes dos valores claramente simétricos (uno para true, otro para false).
- La condición es legible sin paréntesis extraños.
Ejemplos recomendados:
Estos casos se leen mejor con ternario que con if/else, porque ahorran cuatro líneas de boilerplate.
2.5.4 Cuándo NO usar el operador ternario (casos malos)
El operador ternario es horrible cuando:
- La condición es muy larga.
- Anidas ternarios dentro de ternarios.
- Tienes efectos secundarios (llamadas a métodos que cambian estado).
- La lógica no es simétrica (un valor es muy distinto del otro).
Ejemplos a evitar:
2.5.5 Ternario en return: una de sus mejores virtudes
Donde el operador ternario brilla especialmente es en los return (cuando veas métodos en UD4). Permite devolver un valor u otro en una sola línea:
Ese return con ternario es elegante y directo. Su equivalente con if/else ocupa cuatro líneas más:
2.5.6 Ternario vs if/else: criterios de elección
Esta tabla te decide en segundos:
| Si tu caso es... | Usa... |
|---|---|
| Asignación simple entre dos valores simétricos. | Operador ternario. |
Asignación como argumento a una llamada (IO.println(...)). |
Operador ternario. |
return con dos valores. |
Operador ternario. |
| Más de dos ramas posibles. | if / else if / else. |
| Necesitas ejecutar varias sentencias en cada rama. | if / else. |
| Tienes efectos secundarios (llamadas a métodos con impacto). | if / else. |
| La condición es larga o compleja. | if / else con variable booleana intermedia. |
Regla del profesor:
Si dudas, usa
if/else. El ternario es un lujo cuando encaja; elif/elsesiempre funciona. No te obligues a usar ternarios para "ahorrar líneas": a veces, cuatro líneas claras son mejores que una línea densa.
2.6 Errores más comunes del if
Esta sección es oro puro: son los bugs que verás una y otra vez, los que persiguen a los programadores junior durante años. Conocerlos antes de escribirlos te va a ahorrar horas de depuración.
2.6.1 Error 1: = vs ==
El error más antiguo del mundo. = es asignación. == es comparación.
Por qué Java es más amable que C: en C, edad = 18 asigna 18 a edad y devuelve 18, que se interpreta como true (cualquier valor distinto de 0 es verdadero). El if entra siempre, y el bug es silencioso. En Java, el compilador te salva: como la asignación devuelve int y la condición del if debe ser boolean, da error de compilación. Es una de las grandes virtudes de Java frente a C/C++.
Aun así, el error mental persiste: cuando escribas rápido, vas a escribir = en vez de ==. IntelliJ lo marca con un subrayado rojo y te ofrece "Convertir a ==". Acepta siempre.
2.6.2 Error 2: bloques sin llaves (bug de Apple)
Usar llaves siempre evita errores inesperados.
Sin llaves, solo la primera sentencia pertenece al if. La indentación visual te engaña. La solución, como ya sabes: siempre llaves.
2.6.3 Error 3: el dangling else
El dangling else ("else colgando") es una trampa clásica de anidamiento. Java (como C y la mayoría de lenguajes) asocia cada else con el if más cercano sin else. Mira este código:
¿Qué crees que pasa si x = 5 e y = 0? A simple vista parece que "x no es positivo" se ejecutaría cuando x <= 0. Pero no: el else se asocia con el if (y > 0) más cercano, no con el if (x > 0). Entonces, con x = 5 e y = 0:
if (x > 0)→ true, entra.if (y > 0)→ false, no imprime "Ambos positivos".- El
elsese asocia alif (y > 0), así que imprime "x no es positivo" — peroxsí es positivo. Bug.
La solución, otra vez: llaves. Con llaves, el else queda inequívocamente asociado al if que tú quieras:
Con esta versión, si x = 5 e y = 0, no se imprime nada (correcto). Si x = -1, se imprime "x no es positivo" (correcto).
Regla: cuando hay if anidados, siempre llaves y siempre claro con qué if se asocia cada else. Si tienes que pensar más de dos segundos para saber qué else va con qué if, el código está mal escrito.
2.6.4 Error 4: comparar String con ==
Lo vimos en UD1 y en la sección 1, pero se repite porque es el bug de Java más común entre principiantes:
La versión ("admin".equals(rol)) pone el literal primero: si rol es null, no hay NullPointerException. Es una buena práctica defensiva.
Para ignorar mayúsculas/minúsculas, usa .equalsIgnoreCase():
2.6.5 Error 5: el if no es un switch
Si te encuentras escribiendo algo como:
Funciona, pero es ineficiente y verboso. Cuando veas muchos else if comparando el mismo valor contra literales, lo que necesitas es un switch. Lo verás en la sección 3 y verás que en Java 25 es mucho más expresivo.
2.6.6 Error 6: magic numbers en condiciones
Ya lo vimos en la sección 1: un magic number es un número literal sin contexto. El código compila igual, pero es ilegible:
Ahora, cuando dentro de tres meses abras el código, sabrás qué significa cada número. Y si el gobierno sube la edad laboral a 16, solo cambias la constante en un sitio.
2.6.7 Error 7: condiciones negadas (doble negación)
La regla: prefiere condiciones positivas. Si tu condición empieza con !, plantéate si puedes escribirla en positivo. if (!esValido) suele ser mejor como if (esInvalido) (con la condición esInvalido definida con claridad).
2.6.8 Resumen de errores
| Error | Síntoma | Solución |
|---|---|---|
= vs == |
Error de compilación "incompatible types". | Usar == para comparar. |
| Bloque sin llaves | Línea "extra" que se ejecuta siempre. | Siempre llaves. |
| Dangling else | El else se asocia al if equivocado. |
Llaves en todos los if anidados. |
== en String |
El if no entra aunque el contenido sea igual. |
Usar .equals() o .equalsIgnoreCase(). |
if en escalera con un mismo valor |
Código verboso y repetitivo. | Cambiar a switch (sección 3). |
| Magic numbers | El código se lee como un acertijo numérico. | Definir constantes final int. |
| Doble negación | Condición ilegible con dos !. |
Aplicar De Morgan o pensar en positivo. |
2.7 Validación de entrada con IO.readln y if
Esta es la sección más práctica de todas: vas a aprender a pedir datos al usuario de forma robusta, de modo que tu programa no se vuelva loco cuando el usuario escriba algo distinto a lo esperado. En la sección 1 dibujamos el patrón; aquí lo vamos a programar.
2.7.1 Recordar java.lang.IO (de UD1)
En UD1 conociste la clase java.lang.IO, introducida en Java 25 mediante el JEP 512. Es una API moderna y ligera para entrada/salida por consola que viene incluida en el paquete java.lang y, por tanto, no necesita import, se puede usar directamente. Sus cuatro métodos básicos:
| Método | Qué hace | Devuelve |
|---|---|---|
IO.println(texto) |
Imprime texto y salto de línea. |
void |
IO.print(texto) |
Imprime texto sin salto de línea. |
void |
IO.readln() |
Lee una línea completa de teclado (sin el \n final). |
String |
IO.readln(prompt) |
Imprime prompt y luego lee una línea. |
String |
La gran ventaja de IO.readln() frente a Scanner.nextInt() o Scanner.nextDouble() es que siempre devuelve un String limpio: no hay buffer residual, no hay InputMismatchException sorpresivo, no hay que acordarse de "limpiar el salto de línea". Lo que escribió el usuario es exactamente lo que recibes. Tú decides cómo lo conviertes.
La contrapartida, como verás, es que tú eres responsable de la conversión: si pides un int, llamas a Integer.parseInt(IO.readln()). Y si la conversión falla, el programa lanza NumberFormatException. Como todavía no sabemos manejar excepciones (sección 8), vamos a suponer que las entradas son correctas.
En toda esta sección usaremos exclusivamente IO.readln(). Aún existen ejemplos en Internet y libros con Scanner, así que te conviene reconocer ambas formas; pero en tu código, en este curso, la entrada de momento será con IO.readln().
2.7.2 Validación de rango: edad entre 0 y 120
El patrón más común: pedir un número y comprobar que está en un rango válido.
Aquí combinamos:
- Una lectura de un
StringconIO.readln(). - Una conversión a
intconInteger.parseInt(...). - Una validación de rango (edad entre 0 y 120).
- Una clasificación por rangos (menor, adulto, jubilado).
Fíjate que el orden importa: primero descartamos los valores imposibles, luego clasificamos. Si invertimos el orden y ponemos if (edad < 18) primero, ¿qué pasa con edad = -5? Que entraría en "menor de edad", lo cual es absurdo para una edad negativa. Primero valida, luego clasifica.
Si el usuario escribe 'abc', el programa se cae
En este ejemplo, si el usuario escribe "abc", Integer.parseInt("abc") lanza NumberFormatException y el programa termina con error. Hasta la sección 8 (excepciones con try-catch) no sabremos evitarlo. Mientras tanto, en los ejemplos básicos asumiremos que el usuario escribe cosas razonables.
2.7.3 Validación de menú: opción entre 1 y 4
Otro patrón clásico: menú de opciones.
Observa:
- Validamos la opción al principio. Si no es válida, no pedimos operandos. No tiene sentido pedir operandos si el usuario eligió una opción inexistente.
- Para los operandos decimales, usamos
IO.readln().replace(',', '.'): leemos y, si el usuario ha escrito coma como separador decimal, se la cambiamos por punto antes de pasársela aDouble.parseDouble. Es una de las grandes ventajas de leer comoString: podemos normalizar la entrada a nuestro antojo. Lo verás en detalle en 2.7.5. - Cuidado con la división por cero. Java no lanza error de compilación; lanza
ArithmeticException(para enteros) o devuelveInfinity(para dobles). Con unif (b == 0)lo evitamos.
2.7.4 Validación de texto: "Sí" / "No"
Detalles a observar:
IO.readln().trim(): lee toda la línea y quita espacios en blanco por los lados. Si el usuario escribe" sí ", se queda"sí". ConIO.readln()no hay buffer residual: lo que se lee es exactamente la línea completa, sin sorpresas.equalsIgnoreCaseen vez deequals: acepta "S", "s", "SÍ", "sí", "Si", "sI". Más amable con el usuario.- Cubrimos variantes: "s" y "sí" como sinónimos; "n" y "no" como sinónimos. Si el usuario escribe otra cosa, le damos un mensaje claro.
elsefinal para el caso de error: en validación, siempre tiene que haber un "camino" para cuando el dato no encaje.
Este patrón —IO.readln() seguido de .trim() y .equalsIgnoreCase()— es tan limpio que vas a usarlo en casi todos los programas que pidan confirmación al usuario. Acostúmbrate a escribirlo de carrerilla.
2.7.5 La trampa del separador decimal
En España, el separador decimal es la coma (,). En Java, el separador decimal siempre es el punto (.). Y eso da lugar a una trampa:
Si el usuario escribe 1,75 (con coma, como le han enseñado toda la vida), Double.parseDouble("1,75") lanza NumberFormatException y el programa se cae. Tú sabes que es un decimal, pero Java no.
La solución es normalizar la entrada antes de parsear: cambiar la coma por punto con replace(',', '.'):
Ahora el usuario puede escribir 1,75, 1.75, 1.75 o 1,7500: el .trim() quita los espacios y el .replace(',', '.') convierte la coma en punto. Double.parseDouble recibe siempre un texto con punto y funciona.
Patrón recomendado para leer decimales:
Memoriza esta línea: la vas a usar en cada programa que pida un precio, una altura, un peso o cualquier decimal.
2.7.6 Ejemplo completo: pedir y validar edad
Vamos a juntar todo lo aprendido en un ejemplo completo: pedir la edad al usuario, validarla y mostrar un mensaje según el rango.
Salidas posibles según la entrada:
| Entrada | Salida |
|---|---|
"17" |
Eres menor de edad.No estás en edad laboral. |
"35" |
Eres adulto en edad laboral.Puedes trabajar legalmente. |
"70" |
Estás en edad de jubilación.No estás en edad laboral. |
"-5" |
Error: debes escribir un número entero positivo. (no pasa el matches) |
"200" |
Error: la edad debe estar entre 0 y 120. |
2.8 Ejemplos resueltos paso a paso
Vamos a hacer cuatro ejemplos completos, cada uno con un enfoque distinto. Te recomiendo escribirlos tú mismo en IntelliJ y jugar con los valores para ver cómo cambia la salida.
2.8.1 Ejemplo 1: calculadora simple con menú
Puntos a observar:
- Constantes para las opciones del menú:
OP_SUMAR, etc. El código se autodocumenta. replace(',', '.')en los operandos: si el usuario escribe3,14(con coma), lo convertimos a3.14(con punto) antes de parsear. Truco útil para aceptar el formato español sin tener que pelearse con locales.- División por cero controlada con un
if(más adelante, en sección 8, verástry-catch). - No hace falta
importniclose():java.lang.IOya está disponible por defecto en Java 25 y no hay que cerrar ningún recurso. El código queda más limpio que conScanner.
2.8.2 Ejemplo 2: tarifa de taxi
Una empresa de taxis cobra una tarifa base de 2,50 €, más 1,10 € por kilómetro hasta 10 km, y 0,80 € por kilómetro a partir de ahí. Vamos a calcular la tarifa.
Pruebas:
| km | Tarifa |
|---|---|
| 0 | 2,50 € |
| 5 | 8,00 € |
| 10 | 13,50 € |
| 20 | 21,50 € |
| 50 | 45,50 € |
Puntos a observar:
- Constantes con
final: si la empresa cambia las tarifas, solo cambias una línea. String.format("%.2f", precio): imprime el número con dos decimales.%.2fsignifica "número con 2 decimales".replace(',', '.')antes de validar: así el usuario puede escribir5,5o5.5y ambos funcionan.ifconelse(sinelse if) porque solo hay dos casos: por debajo o por encima del umbral.
2.8.3 Ejemplo 3: determinar estación del año
Dado un número de mes (1–12), decir qué estación del año es. Esta es la versión con if encadenado (en la sección 3 verás una versión mucho más elegante con switch).
Puntos a observar:
- Cada
else ifagrupa tres meses con||: aquí el||es la forma natural, porque "es invierno si el mes es 12 O 1 O 2". - El
elsefinal cubre septiembre, octubre, noviembre sin necesidad de escribirlos explícitamente: si las tres primeras condiciones sonfalse, forzosamente es otoño.
2.8.4 Ejemplo 4: sistema de notas con letras
Las universidades anglosajonas usan letras (A, B, C, D, F). Vamos a convertir una nota numérica (0–10) en su letra equivalente.
Puntos a observar:
- Validación de rango antes de clasificar: si la nota está fuera de [0, 10], ni siquiera intentamos clasificarla.
else ifencadenado de mayor a menor: las condiciones se evalúan en orden, así quenota >= 7.0significa "entre 7.0 y 8.999..." (porque las anteriores ya eranfalse). No hace falta escribirnota >= 7.0 && nota < 9.0.elsefinal para "F": si ninguna de las anteriores se cumple, es "F". Esa es la lógica del "por descarte".
2.9 Buenas prácticas y reglas de oro
2.9.1 Las 10 reglas del if profesional
Aquí van las diez reglas que nos autoimponemos en este curso para escribir if como profesionales. Si las sigues, tu código será limpio, legible y mantenible.
- Siempre llaves, aunque el bloque tenga una sola línea. Sin excepciones.
- Indenta con 4 espacios. IntelliJ lo hace solo (
Ctrl+Alt+L). else ifyelsese alinean conif. No los indentes.- Una sola sentencia por línea. No juntes
if (x) { y(); z(); }en una sola línea. - Constantes para magic numbers.
if (edad >= 18)→if (edad >= MAYORIA_EDAD). - Paréntesis en condiciones compuestas.
==para comparar;=para asignar. Si IntelliJ subraya en rojo, mira dos veces..equals()paraString, nunca==. Y si quieres defender contranull, pon el literal primero:"admin".equals(rol).- Prefiere early return a pirámides de
if. Si tienes más de 3 niveles de anidamiento, refactoriza. - Valida la entrada antes de procesarla. Primero comprueba que el dato es válido, luego opera con él.
2.9.2 Cuándo refactorizar (cambiar la estrcutura sin cambiar el funcionamiento) un if
| Síntoma | Refactoriza a |
|---|---|
| Más de 3 niveles de anidamiento. | Early return o extrae un método (UD4). |
5+ else if comparando el mismo valor. |
switch expression (sección 3). |
| Condiciones con dobles negaciones. | De Morgan + pensar en positivo. |
if/else solo para asignar a una variable. |
Operador ternario (si es legible). |
| Condiciones duplicadas en varios sitios. | Variable booleana con nombre. |
Condiciones enormes con &&/|| mezclados. |
Variables booleanas intermedias. |
2.9.3 Comentar el if
Comentarios buenos en un if explican por qué, no qué:
El comentario no debe repetir el código; debe dar contexto que el código no puede expresar.
2.10 Resumen y mapa conceptual
2.10.1 Mapa conceptual
mindmap
root((Sección 2 - if))
Sintaxis
if simple
if con else
if con else if
Anatomía
Condición entre paréntesis
Bloque entre llaves
else if y else alineados
Anidamiento
if anidados
Pirámide de la muerte
Early return
Cláusulas de guarda
Operador ternario
Para asignar
Como return
No anidar
No efectos secundarios
Trampas
= vs ==
Bloques sin llaves
Dangling else
String con ==
Magic numbers
Validación
Rango
Menú
Texto con equals
IO.readln + trim
Separador decimal
matches para enteros
2.10.2 Las 12 ideas que no debes olvidar
- El
ifevalúa una condición booleana entre paréntesis. Sin paréntesis, no compila. ifsimple: se ejecuta o no se ejecuta. No tieneelse.ifconelse: una de las dos ramas se ejecuta siempre. Nunca las dos.else ifencadenado: solo se ejecuta la primera ramatrue. Las demás se saltan.- El
elsefinal es opcional. Si no lo pones y ninguna condición se cumple, no se ejecuta nada. - Las llaves siempre, aunque el bloque tenga una sola línea. Es la regla del bug "goto fail" de Apple.
- Compara
Stringcon.equals(), nunca con==. Y mejor"admin".equals(rol)querol.equals("admin")para defender contranull. - El operador ternario es
if/elseque produce un valor. Úsalo para asignar, no para ejecutar. - Evita la pirámide de la muerte con early return: comprueba lo negativo y sal, en vez de anidar lo positivo.
- Valida primero, procesa después. Primero comprueba que el dato es válido, luego opera con él.
IO.readln()siempre devuelve unStringlimpio: no hay buffer residual niInputMismatchException. Tú decides cómo convertirlo conInteger.parseInt,Double.parseDoubleyreplace(',', '.').
2.11 Actividades propuestas
Cinco actividades graduadas, en orden de dificultad creciente. Hazlas en este orden. Las soluciones se publicarán en Aules cuando el profesor las entregue.
2.11.1 Actividad 1 — Clasificador de edad
Objetivo: practicar if / else if / else con validación de rango.
Enunciado: Escribe un programa ClasificadorEdad que pida al usuario su edad y muestre uno de estos mensajes:
| Edad | Mensaje |
|---|---|
| 0 a 12 | "Infancia" |
| 13 a 17 | "Adolescencia" |
| 18 a 29 | "Juventud" |
| 30 a 64 | "Adultez" |
| 65 a 120 | "Vejez" |
| Fuera de [0, 120] | "Edad no válida" |
Requisitos:
- Usa constantes
final intpara los umbrales. - Lee la entrada con
IO.readln(). - Usa
else ifencadenado para clasificar (no anidamiento). - No hace falta
importniclose():java.lang.IOestá disponible por defecto.
Entrega: archivo ClasificadorEdad.java en el paquete jams.programacion.ud3.
Criterios evaluados: CE 3.1, CE 3.5, CE 3.7.
2.11.2 Actividad 2 — Refactorización de una pirámide
Objetivo: practicar early return y cláusulas de guarda.
Enunciado: Te dan el siguiente código (con pirámide de la muerte):
Tareas:
- Reescríbelo con cláusulas de guarda (early return) de modo que la indentación no crezca.
- Define constantes con
finalpara los mensajes y la edad mínima. - Compara las dos versiones: ¿cuál es más legible? Escribe dos líneas justificando tu elección al final del archivo, en un comentario.
Entrega: archivo RefactorAcceso.java en el paquete jams.programacion.ud3, con la versión original en un comentario de bloque /* ... */ y la versión refactorizada activa.
Criterios evaluados: CE 3.1, CE 3.5, CE 3.7.
2.11.3 Actividad 3 — Calculadora de tarifa de envío
Objetivo: combinar if/else if, constantes y validación con IO.readln.
Enunciado: Una empresa de paquetería cobra:
- 3,50 € de tarifa base.
- 0,90 € por kg hasta 5 kg.
- 1,20 € por kg desde 5 kg hasta 20 kg.
- No se aceptan paquetes de más de 20 kg.
- Si el destino es internacional (preguntar
s/nal usuario), se aplica un recargo del 30 % sobre el total.
Requisitos:
- Pide el peso en kg (decimal) y si el envío es internacional (s/n).
- Valida el peso con un rango [0, 20].
- Valida la respuesta internacional con
.equalsIgnoreCase("s")y.equalsIgnoreCase("n"). Si la respuesta no es válida, muestra error y sale. - Muestra el precio final con dos decimales usando
String.format("%.2f", precio).
Entrega: archivo CalculadoraEnvio.java en el paquete jams.programacion.ud3. Incluye en comentarios al final tres ejemplos de ejecución con sus resultados esperados.
Criterios evaluados: CE 3.1, CE 3.5, CE 3.7.
2.11.4 Actividad 4 — Detector de caracteres
Objetivo: practicar if encadenado con char y String.
Enunciado: Escribe un programa DetectorCaracter que pida al usuario un carácter (un solo carácter, leído con IO.readln().charAt(0)). El programa debe clasificarlo en una de estas categorías:
| Categoría | Criterio |
|---|---|
"Vocal minúscula" |
c == 'a', 'e', 'i', 'o', 'u' |
"Vocal mayúscula" |
c == 'A', 'E', 'I', 'O', 'U' |
"Consonante minúscula" |
Letra minúscula que no sea vocal |
"Consonante mayúscula" |
Letra mayúscula que no sea vocal |
"Dígito" |
c >= '0' && c <= '9' |
"Otro" |
Cualquier otra cosa (espacio, símbolo, etc.) |
Requisitos:
- Valida que el usuario haya escrito al menos un carácter (si no, error y sale).
- Usa
else ifencadenado, no anidamiento. - Para comprobar si es letra, puedes usar
(c >= 'a' && c <= 'z')y(c >= 'A' && c <= 'Z'). (El métodoCharacter.isLetter(c)se verá en UD2.)
Entrega: archivo DetectorCaracter.java en el paquete jams.programacion.ud3.
Criterios evaluados: CE 3.1, CE 3.5, CE 3.7.
2.11.5 Actividad 5 — Cuestionario de repaso
Responde por escrito (no hace falta compilar):
- ¿Cuál es la diferencia entre
ifsimple,ifconelseyifconelse if? - ¿Por qué decimos que las llaves deben ser obligatorias, aunque el bloque tenga una sola línea?
- ¿Qué es el dangling else y cómo se evita?
- ¿Por qué
if (rol == "admin")puede no funcionar aunque el contenido sea"admin"? - Escribe con operador ternario: "asigna a
descuentoel valor0.20siesVipestrue,0.05si no". - ¿Cuándo refactorizarías un
ifanidado con early return? - ¿Por qué
if (edad < 0 || edad > 120)va antes queif (edad < 18)en un programa que clasifica edades? - Nombra dos reglas de oro del
ifprofesional que aplicarás a partir de hoy.
Entrega: archivo cuestionario_seccion2.md con tus respuestas.
Criterios evaluados: CE 3.1, CE 3.7.