🔀 ¿Qué son los flujos de control en Java?
Los flujos de control en Java son las estructuras del lenguaje que permiten alterar el orden secuencial en que se ejecutan las instrucciones de un programa. Por defecto, el intérprete de la JVM ejecuta cada sentencia de arriba a abajo, una tras otra; las estructuras de control introducen bifurcaciones, repeticiones y saltos que hacen posible que un programa tome decisiones, repita tareas y reaccione ante distintas situaciones en tiempo de ejecución.
En la programación estructurada, toda lógica puede expresarse combinando únicamente tres tipos de estructuras: la secuencia (ejecución lineal), la selección (condicionales) y la iteración (bucles). Java implementa estas tres categorías mediante un conjunto bien definido de palabras reservadas: if, else, switch, for, while, do, break, continue y return.
Dominar los flujos de control es un requisito imprescindible para cualquier programador Java, ya que aparecen en prácticamente todas las aplicaciones: desde validar la entrada del usuario con un if, hasta recorrer una colección con un for-each, pasando por mantener un servidor en escucha con un while. A lo largo de este artículo se examinará cada estructura en profundidad, con ejemplos de código compilables, tablas comparativas y ejercicios resueltos.
Este concepto tiene raíces históricas profundas: el teorema de Böhm-Jacopini, demostrado en 1966 por los matemáticos italianos Corrado Böhm y Giuseppe Jacopini, estableció formalmente que cualquier algoritmo puede implementarse utilizando únicamente secuencias, selecciones e iteraciones. Este resultado teórico sentó las bases de la programación estructurada y explica por qué Java —al igual que todos los lenguajes imperativos modernos— incluye precisamente estas tres categorías de estructuras de control.
En el contexto de aplicaciones empresariales, los flujos de control resultan esenciales para implementar reglas de negocio, gestionar transacciones y orquestar procesos complejos. Un sistema bancario, por ejemplo, utiliza condicionales para autorizar o rechazar operaciones según el saldo disponible, bucles para procesar lotes de transacciones al cierre de jornada, y sentencias de salto para interrumpir la ejecución ante condiciones de error. Comprender a fondo estas estructuras no es solo una cuestión académica: constituye la base sobre la que se construye toda la lógica de cualquier aplicación Java profesional.
📊 Tabla resumen de estructuras de control en Java
Antes de profundizar, conviene disponer de una visión global. La siguiente tabla clasifica las estructuras de control disponibles en Java según su categoría, palabra clave y propósito principal.
| Categoría | Estructura | Palabra clave | Descripción |
|---|---|---|---|
| Selección | Condicional simple/doble | if / if-else | Ejecuta un bloque si la condición booleana es verdadera; opcionalmente ejecuta otro bloque si es falsa. |
| Selección | Condicional múltiple | switch | Selecciona entre varios bloques según el valor de una expresión que se compara con constantes. |
| Iteración | Bucle controlado por contador | for | Repite un bloque un número conocido de veces, gestionando inicialización, condición e incremento. |
| Iteración | Bucle de recorrido | for-each | Recorre todos los elementos de un array o colección sin necesidad de índice explícito. |
| Iteración | Bucle con precondición | while | Repite un bloque mientras la condición booleana sea verdadera. Puede no ejecutarse nunca. |
| Iteración | Bucle con postcondición | do-while | Ejecuta el bloque al menos una vez y repite mientras la condición sea verdadera. |
| Salto | Ruptura de bucle | break | Sale inmediatamente del bucle o bloque switch más cercano. |
| Salto | Salto de iteración | continue | Salta al final de la iteración actual y evalúa la condición del bucle de nuevo. |
| Salto | Retorno de método | return | Termina la ejecución del método y, opcionalmente, devuelve un valor al invocador. |
? : y las expresiones lambda permiten tomar decisiones de forma compacta, no se consideran estructuras de control propiamente dichas, sino expresiones que devuelven un valor según una condición.
🔹 La sentencia if-else
La sentencia if-else es la estructura de selección más básica de Java. Permite ejecutar un bloque de código solo cuando una condición booleana se evalúa como true y, opcionalmente, ejecutar un bloque alternativo cuando se evalúa como false.
Sintaxis del if simple
Cuando solo se necesita ejecutar código si la condición se cumple, se emplea el if sin rama else:
public class IfSimple {
public static void main(String[] args) {
int temperatura = 38;
if (temperatura > 37) {
System.out.println("El paciente tiene fiebre.");
}
System.out.println("Fin de la comprobación.");
}
}
// Salida:
// El paciente tiene fiebre.
// Fin de la comprobación.
Sintaxis del if-else
Cuando se necesita una alternativa para el caso en que la condición sea falsa, se añade la cláusula else:
public class IfElseDescuento {
public static void main(String[] args) {
int edad = 15;
double precioBase = 100.0;
double precioFinal;
if (edad < 18) {
precioFinal = precioBase * 0.95; // 5% de descuento para menores
System.out.println("Descuento de menor aplicado.");
} else {
precioFinal = precioBase * 0.85; // 15% de descuento para adultos
System.out.println("Descuento de adulto aplicado.");
}
System.out.println("Precio final: " + precioFinal + " €");
}
}
// Salida:
// Descuento de menor aplicado.
// Precio final: 95.0 €
{ } incluso cuando el bloque contiene una sola sentencia. Esto previene errores difíciles de detectar al añadir más líneas posteriormente.
🔸 if-else anidado y else if
Cuando es necesario evaluar más de dos alternativas, se pueden anidar varias sentencias if-else. La forma más habitual y legible es la escalera else if, que evalúa las condiciones secuencialmente hasta encontrar la primera que sea verdadera:
public class ClasificadorEdad {
public static void main(String[] args) {
int edad = 70;
double descuento;
if (edad < 18) {
descuento = 0.05;
System.out.println("Categoría: Menor de edad");
} else if (edad >= 65) {
descuento = 0.20;
System.out.println("Categoría: Jubilado");
} else {
descuento = 0.15;
System.out.println("Categoría: Adulto");
}
double precioBase = 200.0;
double precioFinal = precioBase * (1 - descuento);
System.out.println("Precio con descuento: " + precioFinal + " €");
}
}
// Salida:
// Categoría: Jubilado
// Precio con descuento: 160.0 €
Es fundamental prestar atención al orden de las condiciones. Si en el ejemplo anterior se evaluara primero edad >= 65 antes de edad < 18, el programa seguiría funcionando correctamente, pero un cambio inadecuado podría provocar que una condición más general «absorba» casos que deberían tratarse de forma específica. La regla general es colocar las condiciones más restrictivas primero.
El operador ternario como alternativa compacta
Para asignaciones simples que dependen de una condición, Java ofrece el operador ternario ? :, que condensa un if-else en una sola expresión:
public class OperadorTernario {
public static void main(String[] args) {
int numero = -5;
String signo = (numero >= 0) ? "positivo o cero" : "negativo";
System.out.println("El número " + numero + " es " + signo);
// Equivalente con if-else:
// String signo;
// if (numero >= 0) {
// signo = "positivo o cero";
// } else {
// signo = "negativo";
// }
}
}
// Salida: El número -5 es negativo
if-else explícito mejora la legibilidad del código.
🔄 La sentencia switch
La sentencia switch evalúa una expresión y transfiere el flujo de ejecución al case cuyo valor coincida con el resultado de dicha expresión. Es especialmente útil cuando se necesita comparar una misma variable contra un conjunto de valores constantes, ya que resulta más legible y eficiente que una cadena larga de else if.
Sintaxis clásica del switch
public class DiaSemana {
public static void main(String[] args) {
int dia = 3;
switch (dia) {
case 1:
System.out.println("Lunes");
break;
case 2:
System.out.println("Martes");
break;
case 3:
System.out.println("Miércoles");
break;
case 4:
System.out.println("Jueves");
break;
case 5:
System.out.println("Viernes");
break;
case 6:
System.out.println("Sábado");
break;
case 7:
System.out.println("Domingo");
break;
default:
System.out.println("Día no válido");
break;
}
}
}
// Salida: Miércoles
Comportamiento fall-through
Si se omite el break al final de un case, la ejecución continúa con las sentencias del siguiente case hasta encontrar un break o el final del switch. Este comportamiento, denominado fall-through, puede aprovecharse de forma intencionada para agrupar casos que comparten la misma lógica:
public class NotaVerbal {
public static void main(String[] args) {
int nota = 7;
switch (nota) {
case 0: case 1: case 2: case 3: case 4:
System.out.println("SUSPENSO");
break;
case 5: case 6:
System.out.println("APROBADO");
break;
case 7: case 8:
System.out.println("NOTABLE");
break;
case 9: case 10:
System.out.println("SOBRESALIENTE");
break;
default:
System.out.println("Nota fuera de rango");
break;
}
}
}
// Salida: NOTABLE
switch con String (desde Java 7)
A partir de Java 7, la sentencia switch admite expresiones de tipo String. La comparación se realiza internamente mediante el método equals(), por lo que es sensible a mayúsculas y minúsculas:
public class SwitchString {
public static void main(String[] args) {
String idioma = "ES";
switch (idioma) {
case "ES":
System.out.println("Hola, bienvenido");
break;
case "EN":
System.out.println("Hello, welcome");
break;
case "FR":
System.out.println("Bonjour, bienvenue");
break;
default:
System.out.println("Idioma no soportado");
break;
}
}
}
// Salida: Hola, bienvenido
| Tipo | ¿Aceptado en switch? | Desde |
|---|---|---|
byte, short, char, int | Sí | Java 1.0 |
Byte, Short, Character, Integer | Sí (autoboxing) | Java 5 |
enum | Sí | Java 5 |
String | Sí | Java 7 |
long, float, double, boolean | No | — |
🔁 El bucle for
El bucle for es la estructura de iteración más utilizada cuando se conoce de antemano el número de repeticiones o se recorre un rango de valores. Su sintaxis integra en una sola línea la inicialización de la variable de control, la condición de continuación y la expresión de incremento, lo que lo convierte en la opción más compacta y legible para iteraciones con contador.
Sintaxis y anatomía
La estructura general del bucle for es: for (inicialización; condición; incremento) { cuerpo }. Las tres partes del encabezado son opcionales, pero los dos punto y coma son obligatorios. El flujo de ejecución es el siguiente: primero se ejecuta la inicialización una sola vez, luego se evalúa la condición; si es verdadera se ejecuta el cuerpo y a continuación el incremento, y el ciclo se repite desde la evaluación de la condición.
public class BucleDivisores {
public static void main(String[] args) {
int numero = 36;
System.out.println("Divisores de " + numero + ":");
for (int i = 1; i <= numero / 2; i++) {
if (numero % i == 0) {
System.out.print(i + " ");
}
}
System.out.println(numero); // El propio número es divisor de sí mismo
}
}
// Salida: Divisores de 36:
// 1 2 3 4 6 9 12 18 36
Variantes del bucle for
El bucle for en Java es muy flexible. Se pueden declarar múltiples variables en la inicialización, usar varias expresiones en el incremento separadas por comas, o incluso dejar vacías una o más de sus tres partes:
public class ForVariantes {
public static void main(String[] args) {
// Múltiples variables e incrementos
for (int i = 0, j = 10; i < j; i++, j--) {
System.out.println("i=" + i + ", j=" + j);
}
System.out.println("---");
// Bucle for sin inicialización ni incremento (equivale a while)
int contador = 5;
for (; contador > 0; ) {
System.out.println("Cuenta atrás: " + contador);
contador--;
}
}
}
// Salida:
// i=0, j=10
// i=1, j=9
// i=2, j=8
// i=3, j=7
// i=4, j=6
// ---
// Cuenta atrás: 5
// Cuenta atrás: 4
// Cuenta atrás: 3
// Cuenta atrás: 2
// Cuenta atrás: 1
Un caso particular es el bucle for infinito, que se escribe como for (;;) omitiendo las tres partes del encabezado. Este patrón se utiliza en servidores, videojuegos o procesos que deben ejecutarse indefinidamente hasta que una condición interna (verificada con break) determine que el programa debe detenerse. Aunque funcional, siempre es preferible diseñar el bucle con una condición de salida explícita para evitar errores difíciles de depurar.
Un aspecto importante del bucle for es el alcance (scope) de la variable de control. Cuando se declara la variable dentro de la inicialización del for —por ejemplo, for (int i = 0; ...)—, esa variable solo existe dentro del bucle y no es accesible una vez finalizado. Esto es una ventaja desde el punto de vista de la encapsulación, ya que evita que variables temporales contaminen el espacio de nombres del método. Si se necesita acceder al valor final del contador después del bucle, la variable debe declararse antes de la estructura for.
🔂 El bucle for-each (enhanced for)
Introducido en Java 5, el bucle for-each simplifica el recorrido de arrays y colecciones que implementan la interfaz Iterable. Su ventaja principal es que elimina la necesidad de un índice explícito, reduciendo el riesgo de errores por acceso fuera de límites (ArrayIndexOutOfBoundsException).
public class ForEachEjemplo {
public static void main(String[] args) {
String[] lenguajes = {"Java", "Python", "C++", "JavaScript", "Go"};
// Recorrido con for-each: más limpio y seguro
System.out.println("Lenguajes de programación:");
for (String lenguaje : lenguajes) {
System.out.println(" - " + lenguaje);
}
// Cálculo de la media de un array numérico
double[] notas = {7.5, 8.0, 6.5, 9.0, 7.0};
double suma = 0;
for (double nota : notas) {
suma += nota;
}
double media = suma / notas.length;
System.out.println("Media de notas: " + media);
}
}
// Salida:
// Lenguajes de programación:
// - Java
// - Python
// - C++
// - JavaScript
// - Go
// Media de notas: 7.6
for clásico.
Internamente, el compilador de Java transforma el bucle for-each en un bucle for con índice (para arrays) o en un bucle que usa un Iterator (para colecciones). Este detalle de implementación no afecta al rendimiento pero sí explica por qué no se pueden eliminar elementos de una colección dentro de un for-each sin obtener una ConcurrentModificationException: el iterador interno detecta que la estructura ha cambiado durante el recorrido.
🔃 El bucle while
El bucle while repite un bloque de sentencias mientras una condición booleana sea verdadera. A diferencia del for, no incluye inicialización ni incremento en su sintaxis, por lo que resulta idóneo cuando el número de iteraciones no se conoce a priori y depende de una condición externa o del estado del programa.
public class WhileSumaDigitos {
public static void main(String[] args) {
int numero = 9876;
int sumaDigitos = 0;
int temporal = numero;
while (temporal > 0) {
sumaDigitos += temporal % 10; // Extraer último dígito
temporal /= 10; // Eliminar último dígito
}
System.out.println("La suma de los dígitos de " + numero
+ " es: " + sumaDigitos);
}
}
// Salida: La suma de los dígitos de 9876 es: 30
Es importante garantizar que la condición del while se volverá falsa en algún momento; de lo contrario el programa entrará en un bucle infinito. Esto ocurre habitualmente cuando se olvida actualizar la variable que controla la condición dentro del cuerpo del bucle.
import java.util.Scanner;
public class WhileValidacion {
public static void main(String[] args) {
Scanner teclado = new Scanner(System.in);
int numero = -1;
// Pedir un número entre 1 y 100 hasta que sea válido
while (numero < 1 || numero > 100) {
System.out.print("Introduce un número entre 1 y 100: ");
numero = teclado.nextInt();
if (numero < 1 || numero > 100) {
System.out.println("Valor fuera de rango. Inténtalo de nuevo.");
}
}
System.out.println("Número aceptado: " + numero);
teclado.close();
}
}
El patrón de validación de entrada con while que se muestra en el ejemplo anterior es uno de los usos más frecuentes de esta estructura. La idea es solicitar datos al usuario repetidamente hasta que proporcione un valor que cumpla los requisitos esperados. Este patrón aparece en formularios de consola, asistentes de configuración, y cualquier programa que necesite garantizar la integridad de los datos antes de procesarlos.
🔄 El bucle do-while
El bucle do-while es una variante del while que garantiza al menos una ejecución del cuerpo antes de evaluar la condición. Esto lo convierte en la estructura ideal para situaciones como mostrar un menú al usuario, donde siempre se necesita presentar las opciones al menos una vez.
import java.util.Scanner;
public class DoWhileMenu {
public static void main(String[] args) {
Scanner teclado = new Scanner(System.in);
int opcion;
do {
System.out.println("\n=== MENÚ PRINCIPAL ===");
System.out.println("1. Ver saldo");
System.out.println("2. Realizar depósito");
System.out.println("3. Realizar retiro");
System.out.println("0. Salir");
System.out.print("Seleccione una opción: ");
opcion = teclado.nextInt();
switch (opcion) {
case 1:
System.out.println("Saldo actual: 1.500,00 €");
break;
case 2:
System.out.println("Depósito realizado.");
break;
case 3:
System.out.println("Retiro realizado.");
break;
case 0:
System.out.println("Hasta pronto.");
break;
default:
System.out.println("Opción no válida.");
break;
}
} while (opcion != 0);
teclado.close();
}
}
La diferencia clave entre while y do-while radica en la ubicación de la evaluación de la condición:
| Característica | while | do-while |
|---|---|---|
| Evaluación de la condición | Antes de cada iteración | Después de cada iteración |
| Ejecuciones mínimas | 0 (si la condición es falsa desde el inicio) | 1 (siempre ejecuta el cuerpo al menos una vez) |
| Uso típico | Lectura de archivos, espera de eventos | Menús interactivos, validación de entrada |
| Punto y coma final | No necesario tras la llave | Obligatorio tras el paréntesis: } while (cond); |
Como regla práctica, conviene preguntarse: «¿Necesito que el bloque se ejecute al menos una vez antes de evaluar la condición?». Si la respuesta es afirmativa —como en menús, solicitudes de credenciales, generación de números aleatorios hasta obtener uno válido o lectura de datos hasta recibir un centinela— el do-while es la estructura adecuada. En caso contrario, el while convencional resulta más explícito y evita una ejecución innecesaria del cuerpo cuando la condición inicial ya es falsa.
⏭️ break, continue y return
Java proporciona tres sentencias de salto que permiten alterar el flujo normal dentro de bucles y métodos. Comprender cuándo y cómo utilizarlas resulta esencial para escribir código eficiente y legible.
La sentencia break
La sentencia break termina inmediatamente la ejecución del bucle o bloque switch que la contiene. El flujo continúa con la primera instrucción después de la estructura interrumpida.
public class BreakEjemplo {
public static void main(String[] args) {
int[] numeros = {4, 7, -2, 15, 3, -8, 10};
// Buscar el primer número negativo
int primerNegativo = 0;
for (int num : numeros) {
if (num < 0) {
primerNegativo = num;
break; // Sale del bucle al encontrar el primero
}
}
System.out.println("Primer negativo encontrado: " + primerNegativo);
}
}
// Salida: Primer negativo encontrado: -2
La sentencia continue
La sentencia continue salta el resto de la iteración actual y pasa directamente a evaluar la condición de la siguiente iteración. Es útil para «saltarse» ciertos elementos sin romper el bucle.
public class ContinueEjemplo {
public static void main(String[] args) {
// Imprimir solo los números pares del 1 al 20
System.out.println("Números pares del 1 al 20:");
for (int i = 1; i <= 20; i++) {
if (i % 2 != 0) {
continue; // Salta los impares
}
System.out.print(i + " ");
}
System.out.println();
// Evitar división por cero con continue
double[] valores = {10, 0, 5, 0, 8};
for (int i = 0; i < valores.length; i++) {
if (valores[i] == 0) {
continue; // Salta la división cuando el divisor es 0
}
double resultado = 100 / valores[i];
System.out.println("100 / " + valores[i] + " = " + resultado);
}
}
}
// Salida:
// Números pares del 1 al 20:
// 2 4 6 8 10 12 14 16 18 20
// 100 / 10.0 = 10.0
// 100 / 5.0 = 20.0
// 100 / 8.0 = 12.5
La sentencia return
La sentencia return finaliza la ejecución del método actual y devuelve el control al punto de invocación. Si el método tiene un tipo de retorno distinto de void, debe ir acompañado de un valor:
public class ReturnEjemplo {
public static boolean esPositivo(int numero) {
if (numero > 0) {
return true;
}
return false;
}
public static int factorial(int n) {
if (n <= 1) {
return 1; // Caso base
}
return n * factorial(n - 1); // Llamada recursiva
}
public static void main(String[] args) {
System.out.println("¿Es positivo 5? " + esPositivo(5));
System.out.println("¿Es positivo -3? " + esPositivo(-3));
System.out.println("Factorial de 6: " + factorial(6));
}
}
// Salida:
// ¿Es positivo 5? true
// ¿Es positivo -3? false
// Factorial de 6: 720
Conviene distinguir claramente el comportamiento de return del de break. Mientras que break solo sale de la estructura de control más cercana (bucle o switch), return abandona todo el método que lo contiene, independientemente de cuántos bucles o bloques condicionales estén activos en ese momento. Por ello, return resulta especialmente útil como alternativa a break con etiquetas: en lugar de saltar fuera de varios bucles anidados, se extrae la lógica a un método auxiliar y se usa return para terminar la búsqueda.
🔗 Bucles anidados y etiquetas
En muchas situaciones es necesario colocar un bucle dentro de otro, lo que se conoce como bucles anidados. Esto es habitual al trabajar con matrices bidimensionales, al generar combinaciones o al buscar elementos en estructuras complejas. Es importante tener en cuenta que la complejidad temporal de los bucles anidados se multiplica: un bucle externo de N iteraciones con uno interno de M iteraciones produce N × M ejecuciones del cuerpo interno, por lo que conviene minimizar el trabajo realizado en los niveles más profundos.
public class TablaMultiplicar {
public static void main(String[] args) {
System.out.println("Tabla de multiplicar (1-5):");
System.out.println(" 1 2 3 4 5");
System.out.println(" -------------------------");
for (int fila = 1; fila <= 5; fila++) {
System.out.printf("%2d |", fila);
for (int columna = 1; columna <= 5; columna++) {
System.out.printf("%4d", fila * columna);
}
System.out.println();
}
}
}
// Salida:
// Tabla de multiplicar (1-5):
// 1 2 3 4 5
// -------------------------
// 1 | 1 2 3 4 5
// 2 | 2 4 6 8 10
// 3 | 3 6 9 12 15
// 4 | 4 8 12 16 20
// 5 | 5 10 15 20 25
Etiquetas (labels) con break y continue
Cuando se trabaja con bucles anidados, break y continue sin etiquetar actúan únicamente sobre el bucle más interno. Si se desea salir de un bucle externo o saltar a su siguiente iteración desde un bucle interno, se utilizan etiquetas:
public class EtiquetasBreak {
public static void main(String[] args) {
int[][] matriz = {
{1, 2, 3},
{4, -1, 6},
{7, 8, 9}
};
int buscado = -1;
int filaEncontrada = -1;
int columnaEncontrada = -1;
busqueda: // Etiqueta del bucle externo
for (int i = 0; i < matriz.length; i++) {
for (int j = 0; j < matriz[i].length; j++) {
if (matriz[i][j] == buscado) {
filaEncontrada = i;
columnaEncontrada = j;
break busqueda; // Sale de AMBOS bucles
}
}
}
if (filaEncontrada != -1) {
System.out.println("Valor " + buscado + " encontrado en ["
+ filaEncontrada + "][" + columnaEncontrada + "]");
} else {
System.out.println("Valor no encontrado.");
}
}
}
// Salida: Valor -1 encontrado en [1][1]
return para salir limpiamente.
🧩 Ejemplo integrador: sistema de menú interactivo
El siguiente programa combina todas las estructuras de control estudiadas en un ejemplo realista: un pequeño sistema de gestión de pedidos con menú interactivo. Se emplean do-while para el bucle del menú, switch para las opciones, for-each para recorrer la lista de pedidos, if-else para validaciones y break para interrumpir búsquedas.
import java.util.Scanner;
import java.util.ArrayList;
public class SistemaPedidos {
static ArrayList<String> pedidos = new ArrayList<>();
static ArrayList<Double> importes = new ArrayList<>();
public static void mostrarPedidos() {
if (pedidos.isEmpty()) {
System.out.println("No hay pedidos registrados.");
return;
}
System.out.println("\n--- Lista de pedidos ---");
double total = 0;
for (int i = 0; i < pedidos.size(); i++) {
System.out.printf(" %d. %-20s %8.2f €%n", i + 1,
pedidos.get(i), importes.get(i));
total += importes.get(i);
}
System.out.printf(" TOTAL: %27.2f €%n", total);
}
public static void agregarPedido(Scanner teclado) {
System.out.print("Producto: ");
String producto = teclado.nextLine();
double importe = -1;
while (importe <= 0) {
System.out.print("Importe (€): ");
importe = Double.parseDouble(teclado.nextLine());
if (importe <= 0) {
System.out.println("El importe debe ser positivo.");
}
}
pedidos.add(producto);
importes.add(importe);
System.out.println("Pedido añadido correctamente.");
}
public static void buscarPedido(Scanner teclado) {
System.out.print("Buscar producto: ");
String busqueda = teclado.nextLine().toLowerCase();
boolean encontrado = false;
for (int i = 0; i < pedidos.size(); i++) {
if (pedidos.get(i).toLowerCase().contains(busqueda)) {
System.out.printf(" Encontrado: %s - %.2f €%n",
pedidos.get(i), importes.get(i));
encontrado = true;
}
}
if (!encontrado) {
System.out.println("No se encontraron coincidencias.");
}
}
public static void main(String[] args) {
Scanner teclado = new Scanner(System.in);
int opcion;
do {
System.out.println("\n=============================");
System.out.println(" SISTEMA DE GESTIÓN PEDIDOS");
System.out.println("=============================");
System.out.println(" 1. Agregar pedido");
System.out.println(" 2. Ver todos los pedidos");
System.out.println(" 3. Buscar pedido");
System.out.println(" 0. Salir");
System.out.print("Opción: ");
opcion = Integer.parseInt(teclado.nextLine());
switch (opcion) {
case 1:
agregarPedido(teclado);
break;
case 2:
mostrarPedidos();
break;
case 3:
buscarPedido(teclado);
break;
case 0:
System.out.println("¡Hasta pronto!");
break;
default:
System.out.println("Opción no válida. Intente de nuevo.");
break;
}
} while (opcion != 0);
teclado.close();
}
}
✅ Buenas prácticas con estructuras de control
Escribir estructuras de control que funcionen correctamente es solo el primer paso. Un código profesional debe ser también legible, mantenible y resistente a errores futuros. Las siguientes recomendaciones, avaladas por las guías de estilo de Oracle y la comunidad Java en general, ayudan a elevar la calidad de cualquier programa que utilice flujos de control.
Usar siempre llaves, incluso con una sola sentencia. Aunque Java permite omitir las llaves cuando el cuerpo del if, for o while contiene una sola instrucción, esta práctica es fuente habitual de errores silenciosos. Al añadir una segunda línea más adelante, el programador puede creer que pertenece al bloque cuando en realidad se ejecuta siempre. Las llaves explicitan el alcance y eliminan la ambigüedad.
Incluir siempre la cláusula default en el switch. Aunque no sea estrictamente obligatorio, el bloque default actúa como red de seguridad para valores inesperados. Es especialmente importante cuando el switch opera sobre datos provenientes del exterior, como la entrada del usuario o el resultado de una llamada a un servicio, donde no se puede garantizar que todos los valores posibles estén cubiertos por los case.
Preferir for-each cuando no se necesita el índice. El bucle for-each reduce el riesgo de errores ArrayIndexOutOfBoundsException y hace el código más declarativo. Solo se recurre al for clásico con índice cuando es necesario modificar elementos del array, acceder a posiciones concretas o iterar en orden inverso.
Limitar la profundidad de anidamiento. Un código con más de tres niveles de anidamiento (un if dentro de un for dentro de otro for) resulta difícil de leer y propenso a errores. Las técnicas habituales para reducir el anidamiento incluyen extraer bloques internos a métodos auxiliares, utilizar early return para descartar casos excepcionales al principio del método, y emplear continue para saltarse iteraciones no relevantes en lugar de anidar condiciones.
Evitar modificar la variable de control dentro del cuerpo del for. La parte de incremento del bucle for existe precisamente para gestionar la variable de control de forma centralizada. Modificarla manualmente dentro del cuerpo introduce confusión y dificulta la predicción del número de iteraciones. Si el flujo requiere avanzar de forma irregular, un bucle while suele ser más apropiado.
⚠️ Errores frecuentes con flujos de control
Incluso programadores con experiencia cometen errores sutiles al trabajar con estructuras de control. Los siguientes son los más habituales y conviene conocerlos para evitarlos desde el principio.
1. Olvidar el break en un switch
Si se omite el break de forma involuntaria, el programa ejecutará los bloques de los case sucesivos (fall-through) hasta encontrar un break o llegar al final del switch. Esto puede producir resultados inesperados.
// ❌ ERROR: falta break en case 1
int opcion = 1;
switch (opcion) {
case 1:
System.out.println("Opción 1 seleccionada");
// ¡Falta break! Se ejecuta también el case 2
case 2:
System.out.println("Opción 2 seleccionada");
break;
}
// Salida INCORRECTA:
// Opción 1 seleccionada
// Opción 2 seleccionada
// ✅ CORRECTO: con break en cada case
switch (opcion) {
case 1:
System.out.println("Opción 1 seleccionada");
break;
case 2:
System.out.println("Opción 2 seleccionada");
break;
}
2. Bucle infinito por variable no actualizada
Si la variable de control del bucle while no se modifica dentro del cuerpo, la condición nunca cambiará y el programa se bloqueará indefinidamente.
// ❌ ERROR: bucle infinito
int i = 0;
while (i < 10) {
System.out.println(i);
// ¡Falta i++! El bucle nunca termina
}
// ✅ CORRECTO
int i = 0;
while (i < 10) {
System.out.println(i);
i++; // Actualizar la variable de control
}
3. Error off-by-one en el bucle for
El error off-by-one se produce cuando el bucle ejecuta una iteración de más o de menos, generalmente por confundir < con <=:
int[] datos = {10, 20, 30, 40, 50};
// ❌ ERROR: ArrayIndexOutOfBoundsException
// for (int i = 0; i <= datos.length; i++) { // <= en vez de <
// ✅ CORRECTO: usar < con .length
for (int i = 0; i < datos.length; i++) {
System.out.println("datos[" + i + "] = " + datos[i]);
}
4. Comparar Strings con == en lugar de equals()
Al usar switch con String no hay problema porque Java usa equals() internamente. Pero en condicionales if, comparar Strings con == comprueba la referencia, no el contenido:
String entrada = new String("hola");
// ❌ ERROR: compara referencias, no contenido
if (entrada == "hola") {
System.out.println("Iguales"); // Puede no ejecutarse
}
// ✅ CORRECTO: usar equals()
if (entrada.equals("hola")) {
System.out.println("Iguales"); // Siempre funciona correctamente
}
// ✅ ALTERNATIVA SEGURA contra NullPointerException
if ("hola".equals(entrada)) {
System.out.println("Iguales");
}
5. Punto y coma accidental tras la condición
Uno de los errores más difíciles de detectar es colocar un punto y coma inmediatamente después del paréntesis de un if, for o while. Java interpreta ese punto y coma como una sentencia vacía, por lo que el bloque entre llaves se ejecuta siempre, independientemente de la condición:
int x = 5;
// ❌ ERROR: el punto y coma convierte el if en sentencia vacía
if (x > 10); // Este punto y coma «cierra» el if
{
System.out.println("Esto se ejecuta SIEMPRE");
}
// ❌ ERROR: el for itera 10 veces sin hacer nada
for (int i = 0; i < 10; i++); // Bucle vacío
{
System.out.println("Solo se ejecuta una vez, fuera del for");
}
// ✅ CORRECTO: sin punto y coma tras la condición
if (x > 10) {
System.out.println("Solo si x es mayor que 10");
}
📝 Ejercicios resueltos
Los siguientes ejercicios permiten practicar todas las estructuras de control estudiadas. Se recomienda intentar resolver cada problema antes de consultar la solución.
Ejercicio 1: Clasificador de triángulos
Enunciado: Escribe un programa que reciba las longitudes de tres lados y determine si forman un triángulo válido. En caso afirmativo, debe clasificarlo como equilátero (tres lados iguales), isósceles (dos lados iguales) o escaleno (todos distintos).
Ejercicio 2: Números primos en un rango
Enunciado: Escribe un programa que encuentre e imprima todos los números primos entre 2 y 100, utilizando un bucle for anidado y la sentencia break.
Ejercicio 3: Patrón de pirámide numérica
Enunciado: Escribe un programa que imprima una pirámide numérica centrada de N filas, donde cada fila muestra los números de 1 hasta el número de fila. Usa bucles anidados.
❓ Preguntas frecuentes sobre Flujos de control en Java: if, switch, for, while explicados
Las dudas más comunes respondidas de forma clara y directa.
💬 Foro de discusión
¿Tienes dudas sobre Flujos de control en Java: if, switch, for, while explicados? Comparte tu pregunta con la comunidad.
Todavía no hay mensajes. ¡Sé el primero en participar!