Java – Parte 2 – Estructuras de Control

Estructuras de Control y Lógica de Programación en Java

En la parte 1 vimos que un programa en Java se ejecuta de manera secuencial: línea por línea, de arriba hacia abajo. Sin embargo, el software real necesita tomar decisiones, repetir tareas y reaccionar a diferentes estados del sistema. Aquí es donde entran las estructuras de control, las cuales nos permiten desviar el flujo de ejecución basándonos en lógica booleana.

2.1 Estructuras Condicionales (if, else if, else) y Ámbito de Variables

La estructura if evalúa una expresión booleana. Si es true, ejecuta un bloque de código; si es false, lo salta o ejecuta una alternativa.

double temperaturaSensor = 45.5;
double umbralAlarma = 40.0;

if (temperaturaSensor > umbralAlarma) {
    // Entramos aquí si la condición es verdadera
    boolean ventiladorEncendido = true;
    System.out.println("ALERTA: Temperatura de " + temperaturaSensor + "C. Ventilador activado.");
} else if (temperaturaSensor > 30.0) {
    System.out.println("Estado normal. Temperatura cálida.");
} else {
    System.out.println("Estado normal. Sistema operando en frío.");
}

// System.out.println(ventiladorEncendido); // ERROR DE COMPILACIÓN

Concepto Avanzado: El Ámbito de Bloque (Block Scope). En Java, las llaves { } definen un «ámbito». Cualquier variable declarada dentro de un bloque if, else o un bucle (como ventiladorEncendido en el ejemplo superior) nace y muere dentro de esas llaves. No puedes acceder a ella desde fuera. Esto protege la memoria y evita colisiones de nombres en programas complejos.

2.2 El Operador Ternario (Condiciones en una sola línea)

Cuando tienes una asignación simple basada en una condición if-else, Java ofrece una sintaxis ultracompacta llamada operador ternario. Es excelente para mantener el código limpio.

Sintaxis: variable = (condición) ? valorSiVerdadero : valorSiFalso;

int voltajeLeido = 3;
// En lugar de hacer un if/else de 4 líneas, lo resolvemos en 1:
String estadoPin = (voltajeLeido >= 3) ? "HIGH (Alto)" : "LOW (Bajo)";

System.out.println("El estado del pin es: " + estadoPin);

2.3 Selección Múltiple (switch clásico y moderno)

Cuando tienes múltiples condiciones basadas en el valor exacto de una sola variable, usar muchos else if hace el código difícil de leer. El switch es la solución ideal.

Tradicionalmente, el switch requiere la palabra break para evitar que la ejecución «caiga» hacia los siguientes casos (comportamiento conocido como fall-through). Imagina que estás evaluando el código de operación (OpCode) de la unidad de control de una CPU:

// Ejemplo con Switch Clásico
int codigoOperacion = 0x02; // Simulando un OpCode en formato hexadecimal

switch (codigoOperacion) {
    case 0x01:
        System.out.println("Instrucción: LOAD. Cargando datos en memoria...");
        break;
    case 0x02:
        System.out.println("Instrucción: ADD. Ejecutando suma lógica en la ALU...");
        break; // Si olvidamos este break, se ejecutará el case 0x03 también
    case 0x03:
        System.out.println("Instrucción: STORE. Guardando resultados...");
        break;
    default:
        System.out.println("OpCode no reconocido por el sistema.");
}

La evolución en Java (Switch Expressions): A partir de Java 14, se introdujo una sintaxis más limpia usando flechas (->) que elimina la necesidad de break y permite que el switch retorne un valor directamente a una variable.

// Ejemplo con Switch Expression (Java 14+)
String instruccion = switch (codigoOperacion) {
    case 0x01 -> "LOAD";
    case 0x02 -> "ADD";
    case 0x03 -> "STORE";
    default -> "UNKNOWN";
};

2.4 Bucles Condicionales (while y do-while)

Los bucles repiten un bloque de código mientras una condición se evalúe como verdadera (true).

  • while (Pre-condición): Evalúa la condición antes de entrar al bucle. Si es falsa desde el inicio, el código nunca se ejecuta.
  • do-while (Post-condición): Ejecuta el bloque de código primero y luego evalúa. Garantiza que el código se ejecutará al menos una vez.

int intentos = 0;
// Bucle while: Esperando una conexión de red
while (intentos < 3) {
    System.out.println("Enviando paquete SYN... Intento " + (intentos + 1));
    intentos++;
}

// Bucle do-while: Ideal para mostrar un menú de interfaz interactivo
int opcionMenu;
do {
    System.out.println("--- MENÚ DE DIAGNÓSTICO ---");
    System.out.println("1. Iniciar Test");
    System.out.println("0. Salir");
    opcionMenu = 0; // En la vida real, capturaríamos la entrada del teclado aquí
} while (opcionMenu != 0);

2.5 Bucles Definidos (for) y Bucles Anidados

El bucle for se utiliza cuando sabemos exactamente cuántas veces queremos iterar. Agrupa la inicialización, la condición y el incremento en una sola línea.

for (int i = 0; i < 5; i++) {
    System.out.println("Analizando sector de memoria " + i);
}

Bucles Anidados (Nested Loops): Es simplemente un bucle dentro de otro bucle. Por cada iteración del bucle exterior, el bucle interior se ejecuta por completo. Son fundamentales cuando trabajamos con matrices bidimensionales, como al programar el barrido de una matriz de LEDs, una pantalla, o procesar imágenes.

// Simulando el barrido de una matriz de 3x3
for (int fila = 0; fila < 3; fila++) {
    for (int columna = 0; columna < 3; columna++) {
        System.out.print("[F:" + fila + " C:" + columna + "] ");
    }
    System.out.println(); // Salto de línea al terminar la fila
}

2.6 Control de Flujo Avanzado (break, continue y Etiquetas)

A veces necesitamos alterar el comportamiento normal de un bucle de manera abrupta:

  • break: Termina el bucle inmediatamente y sale de él.
  • continue: Salta el resto del código en la iteración actual y pasa a la siguiente evaluación del bucle.

Etiquetas (Labels): En bucles anidados, un break normal solo detiene el bucle más interno. Si ocurre un error crítico y necesitas salir de todos los bucles a la vez, puedes nombrar el bucle exterior usando una etiqueta.

busquedaDeErrores: // Esta es la etiqueta
for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
        if (i == 5 && j == 5) {
            System.out.println("Fallo crítico del sistema encontrado en 5,5. Abortando todo.");
            break busquedaDeErrores; // Rompe AMBOS bucles de inmediato
        }
    }
}

2.7 Lógica a Bajo Nivel: Operadores a Nivel de Bits (Bitwise)

Java no solo sirve para interfaces y bases de datos; también puede manipular la memoria a su nivel más profundo: los bits (0s y 1s). Esto es vital si estás procesando protocolos de red, encriptación, o leyendo registros de hardware directamente.

Los operadores bit a bit evalúan cada bit individual de dos números enteros:

  • AND (&): Devuelve 1 solo si ambos bits son 1. Excelente para «enmascarar» (masking) y leer estados específicos.
  • OR (|): Devuelve 1 si al menos uno de los bits es 1. Útil para «encender» bits específicos.
  • XOR (^): Devuelve 1 si los bits son diferentes.
  • NOT (~): Invierte todos los bits (complemento a 1).
  • Desplazamiento (<<, >>): Mueve los bits hacia la izquierda o derecha, lo que equivale a multiplicar o dividir por 2 de forma ultrarrápida a nivel de procesador.

// Ejemplo: Verificando si el tercer bit (indicador de error) está encendido
int registroEstado = 0b00000100; // El prefijo 0b indica número binario (equivale a 4 en decimal)
int mascara = 0b00000100;

// Usamos el operador AND bit a bit
if ((registroEstado & mascara) != 0) {
    System.out.println("Error de hardware detectado en el registro.");
} else {
    System.out.println("Sistema operando correctamente.");
}