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

  1. Del programa lineal al programa que decide
  2. El if simple
  3. El bloque {} y por qué omitirlo es peligroso
  4. if-else: las dos ramas
  5. La escalera else if
  6. Condiciones compuestas y cortocircuito aplicado
  7. Anidamiento, cláusulas de guarda y salida anticipada
  8. if-else frente al operador ternario
  9. Comparar String en condiciones y la trampa de null
  10. Las reglas de negocio de BiblioTech como tabla de decisión
  11. Validar la entrada del usuario con if
  12. Errores Comunes y Consejos
  13. Ejercicios

  1. 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.

  1. El if simple

La forma más básica ejecuta un bloque solo si una condición booleana es verdadera.

if (condicion) {
    // se ejecuta solo si condicion es true
}

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 el if queda 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:

  1. diasRetraso > 0 produce un boolean; ese es el único requisito.
  2. La variable multa está declarada dentro del bloque {}, así que solo existe ahí dentro. Fuera del if no puedes usarla: el compilador dirá cannot find symbol. El ámbito de una variable es el bloque en el que se declara.
  3. "Registro completado." se imprime siempre, haya multa o no: está fuera del if.

  1. El bloque {} y por qué omitirlo es peligroso

Java permite que un if gobierne una sola sentencia sin llaves:

if (diasRetraso > 0)
    System.out.println("Con retraso");

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.

  1. if-else: las dos ramas

Cuando 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 asigna

Si 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.

  1. La escalera else if

Cuando 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 else final 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");
}

  1. 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

  1. 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.

  1. if-else frente al operador ternario

Ya 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";
}

  1. Comparar String en condiciones y la trampa de null

Aquí 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 reventar

La 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.

  1. 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.

  1. Validar la entrada del usuario con if

El 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 entra

Con 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:

if (Math.abs(multa - MULTA_MAXIMA) < 0.001) { ... }   // "practicamente iguales"

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 disponible es true: "Java Efectivo: DISPONIBLE para prestamo inmediato".
  • Si no está disponible y diasRestantes es mayor que 0: "Java Efectivo: PRESTADO, vuelve en N dias".
  • Si no está disponible y diasRestantes es 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 es null o 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:

RECHAZADO: Diego Alonso tiene 4,50 EUR de multas pendientes.

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:

  1. 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.
  2. El orden dentro de cada || es crítico: referencia == null va antes que referencia.isBlank(). Si los intercambiaras, el programa reventaría con null.
  3. Cuando se llega al else if, ambas variables están garantizadas no nulas, así que equalsIgnoreCase es 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

Módulo 2: Flujo de Control

Módulo 3: Programación Orientada a Objetos

Módulo 4: Programación Orientada a Objetos Avanzada

Módulo 5: Estructuras de Datos y Colecciones

Módulo 6: Manejo de Excepciones

Módulo 7: Entrada/Salida de Archivos

Módulo 8: Multihilo y Concurrencia

Módulo 9: Redes

Módulo 10: Temas Avanzados

Módulo 11: Frameworks y Librerías de Java

Módulo 12: Construcción de Aplicaciones del Mundo Real

© Copyright 2026. Todos los derechos reservados