Al cerrar el módulo 1 dejaste BiblioTechApp funcionando, pero con un diagnóstico incómodo: no repite, no decide, no valida y no recuerda nada. Este módulo ataca los tres primeros problemas, y esta lección se ocupa del más urgente: decidir. Hasta ahora tu programa era una línea recta —lee, calcula, imprime— y cualquier "decisión" que tomaba era en realidad un truco aritmético o un operador ternario incrustado en medio de una fórmula. Si un empleado tecleaba -5 días transcurridos, el programa lo aceptaba tan tranquilo. Si el retraso era de 200 días, imprimía la misma frase que si era de 1. Las sentencias condicionales son el mecanismo con el que un programa bifurca su camino: ejecuta unas instrucciones u otras según el estado de los datos. Sin ellas no hay reglas de negocio, no hay validación y no hay software; con ellas, BiblioTechApp empieza a comportarse como una aplicación de verdad.
Contenido
- Del programa lineal al programa que decide
- El
ifsimple - El bloque
{}y por qué omitirlo es peligroso if-else: las dos ramas- La escalera
else if - Condiciones compuestas y cortocircuito aplicado
- Anidamiento, cláusulas de guarda y salida anticipada
if-elsefrente al operador ternario- Comparar
Stringen condiciones y la trampa denull - Las reglas de negocio de BiblioTech como tabla de decisión
- Validar la entrada del usuario con
if - Errores Comunes y Consejos
- Ejercicios
- Del programa lineal al programa que decide
Un programa sin condicionales ejecuta todas sus instrucciones, siempre, en el mismo orden. Eso es lo que hace hoy BiblioTechApp:
flowchart TD
A["Leer datos"] --> B["Calcular retraso"]
B --> C["Calcular multa"]
C --> D["Imprimir recibo"]
Un programa con condicionales tiene caminos alternativos. El mismo recorrido, pero con desvíos:
flowchart TD
A["Leer datos"] --> V{"¿Datos validos?"}
V -- "no" --> E["Avisar del error"]
V -- "si" --> B["Calcular retraso"]
B --> R{"¿Hay retraso?"}
R -- "no" --> P["Recibo sin multa"]
R -- "si" --> M["Calcular multa"]
M --> P
La diferencia es enorme: el número de recorridos posibles deja de ser uno. Y con ello aparece una responsabilidad nueva que te acompañará el resto del curso: cada rama que escribes es una rama que hay que probar.
- El
if simple
if simpleLa forma más básica ejecuta un bloque solo si una condición booleana es verdadera.
Puntos que hay que interiorizar desde el primer día:
- La condición va siempre entre paréntesis.
- La condición debe ser de tipo
boolean. No hay conversión implícita: en Java,if (1)no compila (a diferencia de C o JavaScript). Esto elimina toda una familia de errores. - Después del
)no se pone punto y coma.if (x > 0);compila, pero elifqueda vacío y el bloque siguiente se ejecuta siempre.
Aplicado a BiblioTech:
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
final int DIAS_PRESTAMO = 15;
final double TARIFA_DIARIA = 0.25;
String titulo = "Java Efectivo";
int diasTranscurridos = 27;
int diasRetraso = diasTranscurridos - DIAS_PRESTAMO; // 12
System.out.println("Libro: " + titulo);
if (diasRetraso > 0) {
double multa = diasRetraso * TARIFA_DIARIA;
System.out.printf("Devolucion fuera de plazo. Multa: %.2f EUR%n", multa);
}
System.out.println("Registro completado.");
}
}Observa tres cosas:
diasRetraso > 0produce unboolean; ese es el único requisito.- La variable
multaestá declarada dentro del bloque{}, así que solo existe ahí dentro. Fuera delifno puedes usarla: el compilador dirácannot find symbol. El ámbito de una variable es el bloque en el que se declara. "Registro completado."se imprime siempre, haya multa o no: está fuera delif.
- El bloque
{} y por qué omitirlo es peligroso
{} y por qué omitirlo es peligrosoJava permite que un if gobierne una sola sentencia sin llaves:
Esto compila y funciona. Y es una fuente de bugs tan conocida que la mayoría de las guías de estilo profesionales (Google, Oracle, la de casi cualquier empresa) lo prohíben. El motivo:
if (diasRetraso > 0)
System.out.println("Con retraso");
System.out.println("Aplicando multa..."); // ¡SIEMPRE se ejecuta!La indentación sugiere que las dos líneas dependen del if, pero Java ignora la indentación. Solo la primera sentencia pertenece al if; la segunda se ejecuta siempre, incluso con diasRetraso igual a 0. Este error fue el origen del fallo de seguridad "goto fail" de Apple en 2014, que afectó a millones de dispositivos.
Regla del curso: usa siempre llaves, incluso para una sola línea. El coste son dos caracteres; el beneficio es que añadir una línea más nunca cambiará el significado del código.
if-else: las dos ramas
if-else: las dos ramasCuando hay una alternativa explícita, else recoge todos los casos en los que la condición es falsa:
if (diasRetraso > 0) {
System.out.println("Devolucion FUERA DE PLAZO");
} else {
System.out.println("Devolucion EN PLAZO");
}if y else son mutuamente excluyentes y exhaustivos: exactamente una de las dos ramas se ejecuta, nunca ninguna y nunca las dos. Esa garantía es muy valiosa: si necesitas que una variable quede inicializada pase lo que pase, un if-else te lo asegura y el compilador lo sabe.
String estado; // declarada sin valor
if (diasRetraso > 0) {
estado = "RETRASADO";
} else {
estado = "EN PLAZO";
}
System.out.println(estado); // compila: el compilador sabe que siempre se asignaSi quitaras el else, el compilador daría el error variable estado might not have been initialized, porque existiría un camino en el que la variable llegaría sin valor. El compilador de Java analiza los caminos posibles; conviene tenerlo de aliado.
- La escalera
else if
else ifCuando los casos son más de dos, se encadenan condiciones. Técnicamente no existe una palabra clave elseif: lo que hay es un if dentro del else del anterior, escrito de forma compacta.
if (condicion1) {
// caso 1
} else if (condicion2) {
// caso 2
} else if (condicion3) {
// caso 3
} else {
// ninguno de los anteriores
}Reglas de comportamiento que debes memorizar:
- Las condiciones se evalúan de arriba abajo y se para en la primera verdadera.
- Solo se ejecuta un bloque, aunque varias condiciones sean ciertas.
- El
elsefinal es opcional; si no está, es posible que no se ejecute ninguna rama.
Por eso el orden importa. Esta escalera está mal:
// INCORRECTO: el primer caso se traga todos los demás
if (diasRetraso > 0) {
System.out.println("Retraso leve");
} else if (diasRetraso > 7) {
System.out.println("Retraso grave"); // inalcanzable en la practica
}Con diasRetraso = 20, la primera condición ya es verdadera y se imprime "Retraso leve". La segunda rama nunca se alcanza con valores mayores que 7. La regla práctica: ordena de la condición más restrictiva a la más general.
// CORRECTO
if (diasRetraso > 7) {
System.out.println("Retraso grave");
} else if (diasRetraso > 0) {
System.out.println("Retraso leve");
} else {
System.out.println("Sin retraso");
}
- Condiciones compuestas y cortocircuito aplicado
En el módulo 1 viste &&, ||, ! y el cortocircuito. Aquí los usas de verdad. Recordatorio en una línea: && evalúa el operando derecho solo si el izquierdo es true; || lo evalúa solo si el izquierdo es false.
Eso convierte el orden de los operandos en una herramienta de protección:
String referencia = null;
// El orden salva el programa: si referencia es null, isEmpty() nunca se llama.
if (referencia != null && !referencia.isEmpty()) {
System.out.println("Referencia: " + referencia);
}Si escribieras las condiciones al revés (!referencia.isEmpty() && referencia != null), el programa intentaría llamar a un método sobre null y reventaría. Esa protección tiene nombre propio: guarda de nulidad.
El mismo principio aplicado a BiblioTech, donde un préstamo solo se considera "recuperable con aviso" si hay retraso pero aún no se ha llegado al tope:
double multa = diasRetraso * TARIFA_DIARIA;
if (diasRetraso > 0 && multa < MULTA_MAXIMA) {
System.out.println("Multa parcial: aun no se ha alcanzado el tope.");
}
if (diasRetraso == 0 || diasTranscurridos <= DIAS_PRESTAMO) {
System.out.println("Prestamo cerrado sin incidencias.");
}Y ! para negar, con un consejo: una negación es legible, dos son un acertijo. En lugar de if (!(diasRetraso <= 0)), escribe if (diasRetraso > 0).
| Escrito así | Mejor así | Motivo |
|---|---|---|
if (!(a > b)) |
if (a <= b) |
La negación de una comparación es otra comparación |
if (!(a && b)) |
if (!a || !b) |
Ley de De Morgan; a menudo aún más claro reformular el dominio |
if (esValido == true) |
if (esValido) |
Comparar un booleano con true es redundante |
if (esValido == false) |
if (!esValido) |
Idem |
- Anidamiento, cláusulas de guarda y salida anticipada
Anidar condicionales es meter un if dentro de otro. Funciona, pero degenera rápido en la llamada "flecha de código" (arrow code), en la que la lógica se desplaza a la derecha y se vuelve ilegible:
// Difícil de seguir: hay que recordar tres condiciones a la vez
if (empleado != null) {
if (!empleado.isEmpty()) {
if (diasTranscurridos >= 0) {
if (diasTranscurridos > DIAS_PRESTAMO) {
System.out.println("Procesar multa");
}
}
}
}La técnica que lo arregla se llama cláusula de guarda: en lugar de anidar el caso bueno, se descartan primero los casos malos y se sale. Como en este módulo todo el código vive en main y aún no has visto métodos (llegan en la lección 03-03), la salida se hace con return;, que en main termina el programa.
if (empleado == null || empleado.isEmpty()) {
System.err.println("ERROR: el nombre del empleado es obligatorio.");
return; // termina main
}
if (diasTranscurridos < 0) {
System.err.println("ERROR: los dias transcurridos no pueden ser negativos.");
return;
}
// A partir de aquí, todo lo que sigue es el "camino feliz", sin indentación extra.
if (diasTranscurridos > DIAS_PRESTAMO) {
System.out.println("Procesar multa");
}Compara ambos estilos:
| Aspecto | Anidamiento profundo | Cláusulas de guarda |
|---|---|---|
| Indentación | Crece con cada condición | Se mantiene plana |
| Carga mental | Hay que sostener todas las condiciones en la cabeza | Cada guarda se olvida en cuanto se pasa |
| Mensajes de error | Difíciles de asociar a su causa | Uno por guarda, específico |
| Camino principal | Enterrado al fondo | Visible al final, sin ruido |
Cuando en el módulo 3 aprendas a escribir métodos, este patrón será todavía más natural: cada guarda hará return del método sin abortar el programa entero. La idea, sin embargo, es la misma y conviene adoptarla ya.
if-else frente al operador ternario
if-else frente al operador ternarioYa usaste el ternario condicion ? valorSiTrue : valorSiFalse en el módulo 1 para saturar el retraso a cero. Conviene fijar cuándo usar cada herramienta, porque son intercambiables solo en apariencia.
El ternario es una expresión: produce un valor. El if es una sentencia: ejecuta acciones.
// Ternario: elegir un VALOR. Correcto y legible.
int diasRetraso = (diasTranscurridos > DIAS_PRESTAMO) ? diasTranscurridos - DIAS_PRESTAMO : 0;
String estado = (diasRetraso > 0) ? "RETRASADO" : "EN PLAZO";
// if-else: ejecutar ACCIONES. El ternario aquí no serviría.
if (diasRetraso > 0) {
System.out.println("Notificando al empleado...");
System.out.printf("Importe: %.2f EUR%n", multa);
} else {
System.out.println("Nada que cobrar.");
}| Criterio | Usa ternario | Usa if-else |
|---|---|---|
| Objetivo | Obtener un valor | Ejecutar instrucciones |
| Número de ramas | Exactamente 2 | 2 o más |
| Longitud | Cabe holgadamente en una línea | Varias sentencias por rama |
| Anidamiento | Nunca más de uno | Sin problema |
Inicializar una variable final |
Ideal | Posible, pero más verboso |
El límite práctico está en el anidamiento. Esto es válido pero indefendible en una revisión de código:
// ILEGIBLE: no lo hagas
String gravedad = diasRetraso == 0 ? "NINGUNA" : diasRetraso <= UMBRAL_LEVE ? "LEVE" : "GRAVE";La misma lógica con una escalera se lee sin esfuerzo, y es lo que usarás en BiblioTech:
String gravedad;
if (diasRetraso == 0) {
gravedad = "NINGUNA";
} else if (diasRetraso <= UMBRAL_LEVE) {
gravedad = "LEVE";
} else {
gravedad = "GRAVE";
}
- Comparar
String en condiciones y la trampa de null
String en condiciones y la trampa de nullAquí está el error número uno de quien viene de otros lenguajes. En Java, == sobre objetos compara referencias (si son el mismo objeto en memoria), no contenido. Con String funciona a veces —por el pool de literales que viste en el módulo 1— y falla justo cuando el texto viene del usuario, que es siempre.
String opcion = scanner.nextLine(); // el usuario escribe: salir
if (opcion == "salir") { // MAL: casi siempre false
System.out.println("Adios");
}
if (opcion.equals("salir")) { // BIEN: compara contenido
System.out.println("Adios");
}Métodos útiles en condiciones:
| Método | Qué comprueba | Ejemplo true |
|---|---|---|
a.equals(b) |
Contenido idéntico, sensible a mayúsculas | "salir".equals("salir") |
a.equalsIgnoreCase(b) |
Contenido idéntico ignorando mayúsculas | "Salir".equalsIgnoreCase("SALIR") |
a.isEmpty() |
Longitud cero | "".isEmpty() |
a.isBlank() |
Vacío o solo espacios (Java 11+) | " ".isBlank() |
a.startsWith(b) |
Empieza por ese texto | "978-0000000001".startsWith("978") |
a.contains(b) |
Contiene ese texto | "Java Efectivo".contains("Java") |
Para entradas de usuario, equalsIgnoreCase combinado con trim() evita quejas absurdas:
String respuesta = scanner.nextLine().trim();
if (respuesta.equalsIgnoreCase("si")) {
System.out.println("Confirmado.");
}La trampa de null. Si una variable String vale null, llamar a cualquier método sobre ella detiene el programa con un NullPointerException (verás cómo manejarlo en el módulo 6; por ahora, evítalo). La técnica estándar tiene dos formas:
String referencia = null;
// Opción A: guarda explícita con cortocircuito
if (referencia != null && referencia.equals("REF-001")) { ... }
// Opción B (más elegante): el literal a la izquierda, que nunca es null
if ("REF-001".equals(referencia)) { ... } // false, sin reventarLa opción B se conoce como comparación de Yoda. "REF-001" es un literal, jamás es null, y equals devuelve false limpiamente cuando recibe null como argumento. Úsala cuando el valor pueda ser nulo y no quieras escribir la guarda.
- Las reglas de negocio de BiblioTech como tabla de decisión
Antes de escribir condicionales conviene escribir la tabla de decisión: qué casos existen, cómo se distinguen y qué hace cada uno. Si la tabla está bien, el código sale solo.
Reglas de la biblioteca de Nexus Software (constantes ya fijadas en el módulo 1: DIAS_PRESTAMO = 15, TARIFA_DIARIA = 0.25, MULTA_MAXIMA = 20.0, UMBRAL_LEVE = 7):
| Caso | Condición | estado |
gravedad |
Multa |
|---|---|---|---|---|
| 1. En plazo | diasRetraso == 0 |
EN PLAZO |
NINGUNA |
0,00 € |
| 2. Retraso leve | diasRetraso <= UMBRAL_LEVE |
RETRASADO |
LEVE |
diasRetraso * 0,25 |
| 3. Retraso grave | diasRetraso > UMBRAL_LEVE y multa < tope |
RETRASADO |
GRAVE |
diasRetraso * 0,25 |
| 4. Multa topada | multa calculada >= MULTA_MAXIMA |
RETRASADO |
MAXIMA |
20,00 € |
Fíjate en que el caso 4 atraviesa los casos 2 y 3: no depende de los días sino del importe. Por eso se comprueba después de calcular la multa, no antes. El flujo completo:
flowchart TD
A["diasRetraso = max(0, diasTranscurridos - DIAS_PRESTAMO)"] --> B{"diasRetraso == 0"}
B -- "si" --> C["estado = EN PLAZO<br/>gravedad = NINGUNA<br/>multa = 0"]
B -- "no" --> D["multa = diasRetraso * TARIFA_DIARIA"]
D --> E{"multa >= MULTA_MAXIMA"}
E -- "si" --> F["multa = MULTA_MAXIMA<br/>gravedad = MAXIMA"]
E -- "no" --> G{"diasRetraso <= UMBRAL_LEVE"}
G -- "si" --> H["gravedad = LEVE"]
G -- "no" --> I["gravedad = GRAVE"]
C --> Z["Imprimir recibo"]
F --> Z
H --> Z
I --> Z
Y la traducción directa a código, sustituyendo los ternarios anidados del módulo 1:
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
final int DIAS_PRESTAMO = 15;
final double TARIFA_DIARIA = 0.25;
final double MULTA_MAXIMA = 20.0;
final int UMBRAL_LEVE = 7;
String empleado = "Diego Alonso";
String titulo = "Patrones de Diseno";
String isbn = "978-0000000002";
int diasTranscurridos = 120;
// Retraso saturado a cero: nunca negativo.
int diasRetraso = diasTranscurridos - DIAS_PRESTAMO;
if (diasRetraso < 0) {
diasRetraso = 0;
}
String estado;
String gravedad;
double multa;
if (diasRetraso == 0) {
estado = "EN PLAZO";
gravedad = "NINGUNA";
multa = 0.0;
} else {
estado = "RETRASADO";
multa = diasRetraso * TARIFA_DIARIA;
if (multa >= MULTA_MAXIMA) {
multa = MULTA_MAXIMA; // se aplica el tope
gravedad = "MAXIMA";
} else if (diasRetraso <= UMBRAL_LEVE) {
gravedad = "LEVE";
} else {
gravedad = "GRAVE";
}
}
System.out.println("=== BiblioTech - Nexus Software ===");
System.out.printf("%-14s %s%n", "Empleado:", empleado);
System.out.printf("%-14s %s (%s)%n", "Libro:", titulo, isbn);
System.out.printf("%-14s %d%n", "Dias retraso:", diasRetraso);
System.out.printf("%-14s %s / %s%n", "Estado:", estado, gravedad);
System.out.printf("%-14s %.2f EUR%n", "Multa:", multa);
// Aviso adicional: solo cuando la multa ha llegado al tope.
if (gravedad.equals("MAXIMA")) {
System.out.println();
System.out.println("AVISO: multa topada. Notificar a Recursos Humanos.");
}
}
}Salida con diasTranscurridos = 120 (105 días de retraso, multa teórica 26,25 €):
=== BiblioTech - Nexus Software === Empleado: Diego Alonso Libro: Patrones de Diseno (978-0000000002) Dias retraso: 105 Estado: RETRASADO / MAXIMA Multa: 20,00 EUR AVISO: multa topada. Notificar a Recursos Humanos.
Un detalle de diseño que merece la pena señalar: las tres variables estado, gravedad y multa se declaran antes del if y se asignan dentro. Como todas las ramas terminan asignándolas, el compilador acepta usarlas después. Si eliminaras el else final, dejaría de compilar. Es el compilador verificando tu tabla de decisión.
- Validar la entrada del usuario con
if
ifEl programa del módulo 1 aceptaba cualquier cosa. Ahora ya puedes rechazar lo que no tiene sentido. La estrategia, con cláusulas de guarda, es leer y comprobar de inmediato:
package com.nexussoftware.bibliotech;
import java.util.Scanner;
public class BiblioTechApp {
public static void main(String[] args) {
final int DIAS_PRESTAMO = 15;
Scanner scanner = new Scanner(System.in);
System.out.print("Empleado: ");
String empleado = scanner.nextLine().trim();
if (empleado.isBlank()) {
System.err.println("ERROR: el nombre del empleado no puede estar vacio.");
scanner.close();
return;
}
System.out.print("Titulo del libro: ");
String titulo = scanner.nextLine().trim();
if (titulo.isBlank()) {
System.err.println("ERROR: el titulo no puede estar vacio.");
scanner.close();
return;
}
System.out.print("ISBN: ");
String isbn = scanner.nextLine().trim();
if (!isbn.startsWith("978")) {
System.err.println("ERROR: el ISBN de la biblioteca debe empezar por 978.");
scanner.close();
return;
}
System.out.print("Dias transcurridos: ");
String entradaDias = scanner.nextLine().trim();
int diasTranscurridos = Integer.parseInt(entradaDias);
if (diasTranscurridos < 0) {
System.err.println("ERROR: los dias transcurridos no pueden ser negativos.");
scanner.close();
return;
}
if (diasTranscurridos > 3650) {
System.err.println("ERROR: valor inverosimil (mas de 10 anios).");
scanner.close();
return;
}
System.out.println("Entrada validada. Procesando devolucion de " + empleado + "...");
int diasRetraso = diasTranscurridos - DIAS_PRESTAMO;
if (diasRetraso < 0) {
diasRetraso = 0;
}
System.out.println("Dias de retraso: " + diasRetraso);
scanner.close();
}
}Recorre lo que acabas de conseguir: el programa ya no acepta días negativos, ni nombres vacíos, ni ISBN de otra biblioteca. Y detente en la limitación evidente: al detectar un error, el programa se rinde y termina. Lo razonable sería volver a pedir el dato. Eso requiere repetir, y repetir es exactamente lo que hacen los bucles de la lección siguiente.
Queda un agujero que no puedes tapar todavía: si el usuario escribe veinte en lugar de 20, Integer.parseInt no falla con un if, sino que detiene el programa con un NumberFormatException. Ninguna condición puede evitarlo, porque el error ocurre dentro de la conversión. Ese hueco lo cierra el módulo 6, con excepciones. Por ahora, asume que el usuario teclea dígitos.
Errores Comunes y Consejos
1. El punto y coma fantasma.
if (diasRetraso > 0); // <-- ese ';' cierra el if
{
System.out.println("Con retraso"); // se ejecuta SIEMPRE
}Compila sin avisos y es endiabladamente difícil de ver. Ningún IDE moderno lo deja pasar sin subrayar: hazle caso.
2. Confundir = con ==.
boolean disponible = false;
if (disponible = true) { ... } // ASIGNA true y luego evalua true: siempre entraCon booleanos compila y produce un bug silencioso. Con otros tipos (if (x = 5)) no compila, porque int no es boolean. Escribe if (disponible) a secas y el problema desaparece.
3. Comparar String con ==. Ya lo has visto: usa equals o equalsIgnoreCase. Siempre.
4. Comparar double con ==. Por la precisión binaria que viste en el módulo 1, 0.1 + 0.2 == 0.3 es false. Para importes, compara con una tolerancia o —mejor— compara los enteros de los que derivan:
5. Escaleras con condiciones solapadas y mal ordenadas. Recuerda: de lo más restrictivo a lo más general. Y verifica que cada rama es alcanzable con algún dato real.
6. Olvidar el else final. Si tus ramas asignan una variable, sin else puede quedar sin asignar. El compilador te avisará; no lo silencies inicializando a un valor falso como "" o -1 sin pensarlo, porque entonces el bug pasa a ejecución.
7. Consejo de estilo: extrae las condiciones complejas a variables booleanas con nombre.
// Antes: hay que descifrarlo
if (diasRetraso > UMBRAL_LEVE && multa < MULTA_MAXIMA && !empleado.isBlank()) { ... }
// Después: se lee como una frase
boolean esRetrasoGrave = diasRetraso > UMBRAL_LEVE;
boolean multaBajoTope = multa < MULTA_MAXIMA;
boolean empleadoValido = !empleado.isBlank();
if (esRetrasoGrave && multaBajoTope && empleadoValido) { ... }No cuesta rendimiento apreciable y el código se documenta solo. Además, al depurar (lección 02-05) verás cada condición por separado.
8. Consejo: prueba los límites. Si tu condición es diasRetraso <= UMBRAL_LEVE, prueba con 6, 7 y 8. Los bugs de condicionales viven casi siempre en el borde exacto.
Ejercicios
Ejercicio 1: clasificador de disponibilidad
Escribe un programa que, dadas las variables titulo, disponible (boolean) y diasRestantes (int, días que faltan para que venza el préstamo en curso; vale 0 si el libro está en la estantería), imprima uno de estos mensajes:
- Si
disponibleestrue:"Java Efectivo: DISPONIBLE para prestamo inmediato". - Si no está disponible y
diasRestanteses mayor que 0:"Java Efectivo: PRESTADO, vuelve en N dias". - Si no está disponible y
diasRestanteses 0 o negativo:"Java Efectivo: PRESTADO Y VENCIDO, reclamar al empleado".
Usa una escalera else if y llaves siempre. Prueba con los tres casos.
Ejercicio 2: validación de un préstamo nuevo
Un empleado quiere llevarse un libro. La biblioteca de Nexus Software solo autoriza el préstamo si se cumple todo lo siguiente:
- El nombre del empleado no está en blanco.
- El libro está disponible (
disponible == true). - El empleado no tiene multas pendientes (
multaPendiente == 0.0). - El número de préstamos activos del empleado (
prestamosActivos) es menor que 3.
Escribe el programa con cláusulas de guarda: cada incumplimiento imprime en System.err un mensaje específico y termina con return. Si todo se cumple, imprime la autorización con la fecha de devolución expresada como "Devolver en 15 dias" usando la constante DIAS_PRESTAMO.
Pruébalo con Nuria Vidal, disponible true, multa 0.0 y 1 préstamo activo (debe autorizar), y con Diego Alonso, disponible true, multa 4.50 y 0 préstamos (debe rechazar).
Ejercicio 3: comparación segura de referencias
Dadas dos variables String referencia y String referenciaBuscada, cualquiera de las cuales puede valer null, escribe la lógica que imprima:
"COINCIDE"si ambas tienen el mismo contenido ignorando mayúsculas."SIN REFERENCIA"si alguna de las dos esnullo está en blanco."NO COINCIDE"en cualquier otro caso.
El programa no puede detenerse con un NullPointerException en ninguna combinación posible. Prueba, como mínimo, estas cuatro: ("REF-001", "ref-001"), ("REF-001", "REF-002"), (null, "REF-001"), ("REF-001", null).
Soluciones
Solución 1
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
String titulo = "Java Efectivo";
boolean disponible = false;
int diasRestantes = 4;
// Escalera ordenada: primero el caso más específico (disponible),
// luego los dos subcasos de "no disponible".
if (disponible) {
System.out.println(titulo + ": DISPONIBLE para prestamo inmediato");
} else if (diasRestantes > 0) {
System.out.println(titulo + ": PRESTADO, vuelve en " + diasRestantes + " dias");
} else {
System.out.println(titulo + ": PRESTADO Y VENCIDO, reclamar al empleado");
}
}
}Salida con los valores del ejemplo: Java Efectivo: PRESTADO, vuelve en 4 dias.
Prueba los otros dos casos cambiando disponible a true y, después, dejando disponible = false con diasRestantes = 0. Observa que no hace falta escribir else if (!disponible && diasRestantes > 0): al estar en el else, ya sabemos que disponible es false. Repetir la condición sería ruido.
Solución 2
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
final int DIAS_PRESTAMO = 15;
final int MAX_PRESTAMOS = 3;
String empleado = "Nuria Vidal";
String titulo = "Refactorizacion";
String isbn = "978-0000000003";
boolean disponible = true;
double multaPendiente = 0.0;
int prestamosActivos = 1;
// --- Cláusulas de guarda: se descartan primero los casos inválidos ---
if (empleado == null || empleado.isBlank()) {
System.err.println("RECHAZADO: el nombre del empleado es obligatorio.");
return;
}
if (!disponible) {
System.err.println("RECHAZADO: '" + titulo + "' no esta disponible.");
return;
}
// Comparación de double con tolerancia en lugar de != 0.0
if (multaPendiente > 0.001) {
System.err.printf("RECHAZADO: %s tiene %.2f EUR de multas pendientes.%n",
empleado, multaPendiente);
return;
}
if (prestamosActivos >= MAX_PRESTAMOS) {
System.err.printf("RECHAZADO: %s ya tiene %d prestamos activos (maximo %d).%n",
empleado, prestamosActivos, MAX_PRESTAMOS);
return;
}
// --- Camino feliz: sin indentación extra, sin condiciones pendientes ---
System.out.println("=== PRESTAMO AUTORIZADO ===");
System.out.printf("%-12s %s%n", "Empleado:", empleado);
System.out.printf("%-12s %s (%s)%n", "Libro:", titulo, isbn);
System.out.printf("%-12s Devolver en %d dias%n", "Plazo:", DIAS_PRESTAMO);
System.out.printf("%-12s %d de %d%n", "Prestamos:", prestamosActivos + 1, MAX_PRESTAMOS);
}
}Salida con Nuria Vidal:
=== PRESTAMO AUTORIZADO === Empleado: Nuria Vidal Libro: Refactorizacion (978-0000000003) Plazo: Devolver en 15 dias Prestamos: 2 de 3
Con empleado = "Diego Alonso" y multaPendiente = 4.50, la tercera guarda dispara y el programa imprime en System.err:
Lo importante del ejercicio: cuatro condiciones y cero anidamiento. Cada guarda es independiente, tiene su mensaje propio y puede añadirse o quitarse sin tocar las demás.
Solución 3
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
String referencia = "REF-001";
String referenciaBuscada = null;
// Guarda de nulidad con cortocircuito: si la primera parte de cada
// '||' es cierta, isBlank() nunca llega a llamarse sobre null.
boolean faltaAlguna = referencia == null || referencia.isBlank()
|| referenciaBuscada == null || referenciaBuscada.isBlank();
if (faltaAlguna) {
System.out.println("SIN REFERENCIA");
} else if (referencia.equalsIgnoreCase(referenciaBuscada)) {
System.out.println("COINCIDE");
} else {
System.out.println("NO COINCIDE");
}
}
}Resultados de las cuatro combinaciones pedidas:
referencia |
referenciaBuscada |
Salida |
|---|---|---|
"REF-001" |
"ref-001" |
COINCIDE |
"REF-001" |
"REF-002" |
NO COINCIDE |
null |
"REF-001" |
SIN REFERENCIA |
"REF-001" |
null |
SIN REFERENCIA |
Claves de la solución:
- La condición compuesta se extrae a
faltaAlguna, un booleano con nombre. Se lee mejor y, al depurar, podrás inspeccionar su valor de un vistazo. - El orden dentro de cada
||es crítico:referencia == nullva antes quereferencia.isBlank(). Si los intercambiaras, el programa reventaría connull. - Cuando se llega al
else if, ambas variables están garantizadas no nulas, así queequalsIgnoreCasees seguro. Si la escalera estuviera al revés, no lo sería.
Conclusión
Tu programa ya decide. Has visto el if simple y por qué las llaves nunca deben omitirse; el if-else y la garantía de exhaustividad que le da al compilador; la escalera else if, en la que el orden de las condiciones es la lógica; las condiciones compuestas con cortocircuito usadas como guardas de nulidad; las cláusulas de guarda como antídoto contra el anidamiento; el reparto de papeles entre el ternario (elegir un valor) y el if-else (ejecutar acciones); la comparación correcta de String con equals y equalsIgnoreCase, incluida la comparación de Yoda para sobrevivir a null; y, sobre todo, el hábito de escribir primero la tabla de decisión y después el código, que es lo que ha convertido las cuatro reglas de negocio de BiblioTech en una escalera limpia y verificable.
BiblioTechApp clasifica correctamente los cuatro casos de multa, avisa cuando se alcanza el tope y rechaza las entradas absurdas. Pero se rinde a la primera: cuando detecta un dato inválido, termina en lugar de volver a preguntar, y sigue procesando una sola devolución por ejecución.
En la siguiente lección, Bucles, romperás esa barrera. Con while, do-while y for tu programa podrá repetir un bloque tantas veces como haga falta: reintentar la lectura hasta que el dato sea válido, recorrer todo el catálogo de la biblioteca y acumular las multas de una tanda entera de devoluciones sin duplicar una sola línea de código.
Curso de Programación en Java
Módulo 1: Introducción a Java
- Introducción a Java
- Configuración del Entorno de Desarrollo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Operadores
- Entrada y Salida por Consola
- Tu Primer Programa Completo: BiblioTech
Módulo 2: Flujo de Control
- Sentencias Condicionales
- Bucles
- Sentencias Switch
- Break y Continue
- Depuración y Trazas de Ejecución
- Proyecto: Menú Interactivo de BiblioTech
Módulo 3: Programación Orientada a Objetos
- Introducción a la POO
- Clases y Objetos
- Métodos
- Constructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
- La Clase Object: equals, hashCode y toString
Módulo 4: Programación Orientada a Objetos Avanzada
- Interfaces
- Clases Abstractas
- Clases Internas
- Clases Anónimas
- Expresiones Lambda
- Interfaces Funcionales y Referencias a Métodos
- Enumeraciones y Registros
Módulo 5: Estructuras de Datos y Colecciones
- Arreglos
- El Framework de Colecciones
- ArrayList
- LinkedList
- HashMap
- HashSet
- Cola y Deque
- Pila
- Ordenación y Búsqueda en Colecciones
Módulo 6: Manejo de Excepciones
- Introducción a las Excepciones
- Bloque Try-Catch
- Throw y Throws
- Excepciones Personalizadas
- Bloque Finally
- Try-with-resources y AutoCloseable
- Estrategias de Manejo de Errores y Logging
Módulo 7: Entrada/Salida de Archivos
- Lectura de Archivos
- Escritura de Archivos
- Flujos de Archivos
- BufferedReader y BufferedWriter
- Serialización
- La API NIO.2: Path y Files
- Formatos de Intercambio: CSV y Properties
Módulo 8: Multihilo y Concurrencia
- Introducción al Multihilo
- Creación de Hilos
- Ciclo de Vida de un Hilo
- Sincronización
- Utilidades de Concurrencia
- Colecciones Concurrentes y Variables Atómicas
- Tareas Asíncronas con CompletableFuture
Módulo 9: Redes
- Introducción a las Redes
- Sockets
- ServerSocket
- DatagramSocket y DatagramPacket
- URL y HttpURLConnection
- El Cliente HTTP Moderno
Módulo 10: Temas Avanzados
- Genéricos
- Anotaciones
- Reflexión
- Características de Java 8: Streams y Optional
- Fechas y Horas con java.time
- Java 9 y Más Allá
- Memoria, Recolección de Basura y Rendimiento
Módulo 11: Frameworks y Librerías de Java
- Introducción a los Frameworks de Java
- Spring Framework
- Hibernate
- JUnit
- Maven
- Pruebas Avanzadas con Mockito
- Librerías Esenciales del Ecosistema
