Java

Tutorial Java: Estructuras de control

Java estructuras control: manejo y ejemplos. Domina las estructuras de control en Java con ejemplos prácticos y detallados.

Aprende Java y certifícate

Control condicional con if, else if y else

Las estructuras de control condicional permiten ejecutar bloques de código específicos dependiendo de si se cumplen ciertas condiciones. En Java, la estructura condicional más básica y utilizada es el if, que puede complementarse con else if y else para manejar múltiples condiciones.

Estructura básica del if

La sintaxis básica de una sentencia if se compone de la palabra clave if, seguida de una condición entre paréntesis y un bloque de código entre llaves que se ejecutará solo si la condición es verdadera:

if (condición) {
    // Código que se ejecuta si la condición es verdadera
}

La condición debe ser una expresión que se evalúe a un valor booleano (true o false). Si la condición es verdadera, se ejecuta el bloque de código dentro de las llaves; si es falsa, se omite este bloque y la ejecución continúa con el código que sigue después del bloque if.

int edad = 18;

if (edad >= 18) {
    System.out.println("Eres mayor de edad");
}

En este ejemplo, el mensaje se mostrará solo si la variable edad es mayor o igual a 18.

Uso de else

La cláusula else se utiliza para especificar un bloque de código que se ejecutará cuando la condición del if sea falsa:

if (condición) {
    // Código que se ejecuta si la condición es verdadera
} else {
    // Código que se ejecuta si la condición es falsa
}

Veamos un ejemplo práctico:

int hora = 20;

if (hora < 12) {
    System.out.println("Buenos días");
} else {
    System.out.println("Buenas tardes/noches");
}

En este caso, si la hora es menor que 12, se muestra "Buenos días"; de lo contrario, se muestra "Buenas tardes/noches".

Uso de else if

Cuando se necesita evaluar múltiples condiciones en secuencia, se utiliza la estructura else if:

if (condición1) {
    // Código que se ejecuta si condición1 es verdadera
} else if (condición2) {
    // Código que se ejecuta si condición1 es falsa y condición2 es verdadera
} else {
    // Código que se ejecuta si todas las condiciones anteriores son falsas
}

La cláusula else if permite evaluar una nueva condición solo si las condiciones anteriores han sido falsas. Se pueden encadenar tantas cláusulas else if como sea necesario.

int hora = 14;

if (hora < 12) {
    System.out.println("Buenos días");
} else if (hora < 20) {
    System.out.println("Buenas tardes");
} else {
    System.out.println("Buenas noches");
}

En este ejemplo, se evalúan las condiciones en orden:
1. Si hora es menor que 12, se muestra "Buenos días"

2. Si no se cumple lo anterior pero hora es menor que 20, se muestra "Buenas tardes"

3. Si ninguna de las condiciones anteriores se cumple, se muestra "Buenas noches"

Condiciones anidadas

También es posible anidar estructuras condicionales, es decir, incluir una estructura if-else dentro de otra:

int edad = 18;
boolean tieneCarnet = true;

if (edad >= 18) {
    if (tieneCarnet) {
        System.out.println("Puede conducir");
    } else {
        System.out.println("No puede conducir porque no tiene carnet");
    }
} else {
    System.out.println("No puede conducir porque es menor de edad");
}

En este ejemplo, primero se verifica si la persona es mayor de edad. Solo si cumple esta condición, se evalúa si tiene carnet de conducir.

Operadores lógicos en condiciones

Las condiciones pueden hacerse más complejas utilizando operadores lógicos como && (AND), || (OR) y ! (NOT):

int edad = 25;
double salario = 1500.0;

if (edad > 18 && salario >= 1000.0) {
    System.out.println("Cumple los requisitos para el préstamo");
}

if (edad < 18 || salario < 1000.0) {
    System.out.println("No cumple los requisitos para el préstamo");
}

if (!(edad < 18)) {
    System.out.println("No es menor de edad");
}

Estos operadores permiten combinar múltiples condiciones en una sola expresión, lo que puede hacer el código más conciso.

If con una sola instrucción

Si el bloque de código a ejecutar contiene una única instrucción, las llaves son opcionales:

if (edad >= 18)
    System.out.println("Eres mayor de edad");
else
    System.out.println("Eres menor de edad");

Sin embargo, se recomienda siempre utilizar llaves para mejorar la legibilidad y evitar errores, especialmente cuando se modifica el código posteriormente.

Operador ternario

Java ofrece una forma abreviada de escribir ciertas estructuras if-else mediante el operador ternario ? ::

// Estructura if-else convencional
String mensaje;
if (edad >= 18) {
    mensaje = "Mayor de edad";
} else {
    mensaje = "Menor de edad";
}

// Equivalente con operador ternario
String mensaje = (edad >= 18) ? "Mayor de edad" : "Menor de edad";

El operador ternario evalúa la condición antes del signo de interrogación. Si es verdadera, devuelve el valor después del signo de interrogación; si es falsa, devuelve el valor después de los dos puntos.

Buenas prácticas

  • Simplicidad: Mantener las condiciones lo más simples posible para facilitar la lectura.
  • Uso de llaves: Siempre utilizar llaves, incluso para bloques de una sola instrucción.
  • Orden lógico: Organizar las condiciones else if de manera lógica, generalmente de la más específica a la más general.
  • Evitar anidamiento excesivo: Demasiados niveles de anidamiento pueden hacer que el código sea difícil de leer y mantener.
// Ejemplo de código bien estructurado
int puntuación = 85;
String calificación;

if (puntuación >= 90) {
    calificación = "Sobresaliente";
} else if (puntuación >= 70) {
    calificación = "Notable";
} else if (puntuación >= 60) {
    calificación = "Bien";
} else if (puntuación >= 50) {
    calificación = "Suficiente";
} else {
    calificación = "Insuficiente";
}

System.out.println("Tu calificación es: " + calificación);

En este ejemplo, las condiciones están organizadas de mayor a menor, lo que hace que el código sea claro, ya que se detiene en la primera condición que se cumple.

Casos de uso comunes

  • Validación de datos: Verificar si los datos de entrada cumplen ciertos criterios.
  • Control de flujo: Dirigir la ejecución del programa por diferentes caminos según las condiciones.
  • Manejo de errores: Comprobar posibles errores y tomar medidas apropiadas.
// Ejemplo de validación de datos
String usuario = "admin";
String contraseña = "1234";

if (usuario == null || usuario.isEmpty()) {
    System.out.println("El nombre de usuario no puede estar vacío");
} else if (contraseña == null || contraseña.length() < 6) {
    System.out.println("La contraseña debe tener al menos 6 caracteres");
} else {
    System.out.println("Datos válidos, procesando...");
}

Las estructuras condicionales if, else if y else son fundamentales en la programación Java, ya que permiten crear programas que toman decisiones basadas en condiciones.

Control condicional con switch y switch mejorado

La estructura switch proporciona una forma alternativa de controlar el flujo de ejecución cuando se necesita evaluar una variable contra múltiples valores posibles. A diferencia del if-else, que evalúa expresiones booleanas, el switch tradicional compara una expresión con varios valores constantes y ejecuta el bloque de código correspondiente al valor que coincide.

Switch tradicional

La sintaxis básica del switch tradicional es la siguiente:

switch (expresión) {
    case valor1:
        // Código a ejecutar si expresión == valor1
        break;
    case valor2:
        // Código a ejecutar si expresión == valor2
        break;
    // Más casos...
    default:
        // Código a ejecutar si ningún caso coincide
}

La expresión evaluada en el switch debe ser de tipo byte, short, char, int, enum, String (desde Java 7) o sus clases envoltorio. El bloque default es opcional y se ejecuta cuando ninguno de los casos coincide con el valor de la expresión.

Veamos un ejemplo práctico:

int dia = 3;
String nombreDia;

switch (dia) {
    case 1:
        nombreDia = "Lunes";
        break;
    case 2:
        nombreDia = "Martes";
        break;
    case 3:
        nombreDia = "Miércoles";
        break;
    case 4:
        nombreDia = "Jueves";
        break;
    case 5:
        nombreDia = "Viernes";
        break;
    case 6:
        nombreDia = "Sábado";
        break;
    case 7:
        nombreDia = "Domingo";
        break;
    default:
        nombreDia = "Día inválido";
}

System.out.println("El día " + dia + " es " + nombreDia);

En este ejemplo, se evalúa la variable dia y se asigna el nombre correspondiente según su valor.

La importancia del break

La palabra clave break es crucial en el switch tradicional. Si se omite, la ejecución "cae" al siguiente caso sin comprobar su condición, lo que se conoce como fall-through:

int opción = 2;

switch (opción) {
    case 1:
        System.out.println("Opción 1 seleccionada");
        // Sin break, continúa al siguiente caso
    case 2:
        System.out.println("Opción 2 seleccionada");
        // Sin break, continúa al siguiente caso
    case 3:
        System.out.println("Opción 3 seleccionada");
        break;
    default:
        System.out.println("Opción no válida");
}

En este ejemplo, si opción es 2, se imprimirán tanto "Opción 2 seleccionada" como "Opción 3 seleccionada", porque falta el break después del caso 2.

Aunque el fall-through suele ser una fuente de errores, a veces se utiliza intencionadamente para casos que comparten el mismo código:

char calificación = 'B';

switch (calificación) {
    case 'A':
    case 'B':
    case 'C':
        System.out.println("Aprobado");
        break;
    case 'D':
    case 'F':
        System.out.println("Suspendido");
        break;
    default:
        System.out.println("Calificación no válida");
}

En este ejemplo, las calificaciones A, B y C comparten el mismo resultado: "Aprobado".

Switch con String

Desde Java 7, se puede utilizar String como expresión en un switch:

String fruta = "manzana";

switch (fruta.toLowerCase()) {
    case "manzana":
        System.out.println("Las manzanas son rojas o verdes");
        break;
    case "plátano":
        System.out.println("Los plátanos son amarillos");
        break;
    case "naranja":
        System.out.println("Las naranjas son anaranjadas");
        break;
    default:
        System.out.println("No conozco ese tipo de fruta");
}

Es importante tener en cuenta que la comparación de cadenas en un switch distingue entre mayúsculas y minúsculas. En el ejemplo anterior, se utiliza toLowerCase() para evitar problemas con la capitalización.

Switch con enumeraciones (enum)

El switch es útil con enumeraciones, ya que proporciona una forma clara de manejar todos los valores posibles:

enum DiaSemana { LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO }

DiaSemana hoy = DiaSemana.MIERCOLES;

switch (hoy) {
    case LUNES:
    case MARTES:
    case MIERCOLES:
    case JUEVES:
    case VIERNES:
        System.out.println("Es un día laborable");
        break;
    case SABADO:
    case DOMINGO:
        System.out.println("Es fin de semana");
        break;
}

Nótese que al usar enumeraciones en un switch, no es necesario calificar los valores del caso con el nombre de la enumeración.

Switch mejorado (expresiones switch)

A partir de Java 12, se introdujo el switch mejorado o expresiones switch, que resuelve varios problemas del switch tradicional:

  • Elimina la necesidad de break explícito
  • Permite que el switch devuelva un valor
  • Introduce una sintaxis más concisa con el operador de flecha (->)
  • Permite agrupar múltiples casos con una sola acción

La sintaxis básica es:

// Como expresión (devuelve un valor)
tipo variable = switch (expresión) {
    case valor1 -> expresión1;
    case valor2 -> expresión2;
    // Más casos...
    default -> expresiónPorDefecto;
};

// Como sentencia (sin devolver valor)
switch (expresión) {
    case valor1 -> { sentencias1; }
    case valor2 -> sentencia2;
    // Más casos...
    default -> sentenciaPorDefecto;
}

Veamos el ejemplo de los días de la semana con el switch mejorado:

int dia = 3;
String nombreDia = switch (dia) {
    case 1 -> "Lunes";
    case 2 -> "Martes";
    case 3 -> "Miércoles";
    case 4 -> "Jueves";
    case 5 -> "Viernes";
    case 6 -> "Sábado";
    case 7 -> "Domingo";
    default -> "Día inválido";
};

System.out.println("El día " + dia + " es " + nombreDia);

Observe cómo el código es más conciso y claro. No hay break y el valor se asigna directamente.

Múltiples casos en switch mejorado

Se pueden agrupar múltiples casos que comparten la misma acción:

DiaSemana hoy = DiaSemana.MIERCOLES;

String tipoDeJornada = switch (hoy) {
    case LUNES, MARTES, MIERCOLES, JUEVES, VIERNES -> "Día laborable";
    case SABADO, DOMINGO -> "Fin de semana";
};

System.out.println(hoy + " es " + tipoDeJornada);

Bloques de código en switch mejorado

Si se necesita ejecutar múltiples sentencias para un caso, se pueden utilizar bloques de código con la palabra clave yield para devolver un valor:

int mes = 7;
String estación = switch (mes) {
    case 12, 1, 2 -> {
        System.out.println("Hace frío");
        yield "Invierno";
    }
    case 3, 4, 5 -> {
        System.out.println("Las flores florecen");
        yield "Primavera";
    }
    case 6, 7, 8 -> {
        System.out.println("Hace calor");
        yield "Verano";
    }
    case 9, 10, 11 -> {
        System.out.println("Las hojas caen");
        yield "Otoño";
    }
    default -> {
        System.out.println("Mes inválido");
        yield "Desconocido";
    }
};

System.out.println("El mes " + mes + " corresponde a " + estación);

La palabra clave yield se utiliza para devolver un valor desde un bloque de código en una expresión switch.

Pattern matching en switch (Java 17+)

A partir de Java 17, se introdujo el pattern matching en switch, que permite comprobar el tipo de un objeto y extraer sus componentes en una sola operación:

Object obj = "Hola mundo";

String resultado = switch (obj) {
    case Integer i -> "Es un entero: " + i;
    case String s -> "Es una cadena de longitud: " + s.length();
    case Double d -> "Es un número decimal: " + d;
    case null -> "Es null";
    default -> "Es otro tipo: " + obj.getClass().getSimpleName();
};

System.out.println(resultado);

En este ejemplo, se comprueba el tipo de obj y se extrae su valor en una variable local (i, s, d) que puede utilizarse en la expresión del caso.

Pattern matching con guardas

También se pueden añadir condiciones de guarda utilizando la palabra clave when:

Object obj = 42;

String mensaje = switch (obj) {
    case String s when s.length() > 5 -> "Es una cadena larga";
    case String s -> "Es una cadena corta";
    case Integer i when i > 0 -> "Es un entero positivo";
    case Integer i when i < 0 -> "Es un entero negativo";
    case Integer i -> "Es cero";
    default -> "Es otro tipo";
};

System.out.println(mensaje);

Las guardas permiten refinar aún más las condiciones de los casos.

Comparación entre if-else y switch

El switch es más adecuado cuando:

  • Se compara una única variable contra valores constantes
  • Hay múltiples valores posibles para comparar
  • Los casos son mutuamente excluyentes

El if-else es preferible cuando:

  • Se evalúan condiciones complejas o rangos
  • Se comparan diferentes variables
  • Las condiciones no son mutuamente excluyentes
// Mejor con switch
int opción = 2;
switch (opción) {
    case 1 -> System.out.println("Opción 1");
    case 2 -> System.out.println("Opción 2");
    case 3 -> System.out.println("Opción 3");
    default -> System.out.println("Otra opción");
}

// Mejor con if-else
int edad = 25;
double salario = 1500.0;
if (edad > 18 && salario >= 1000.0) {
    System.out.println("Cumple los requisitos");
} else {
    System.out.println("No cumple los requisitos");
}

Buenas prácticas

  • Usar el switch mejorado cuando sea posible, ya que evita errores comunes y hace el código más legible.
  • Incluir siempre un caso default para manejar valores inesperados.
  • Evitar efectos secundarios en las expresiones de los casos.
  • Preferir enumeraciones sobre constantes numéricas para mejorar la legibilidad y seguridad del código.
  • Considerar el rendimiento: para un gran número de casos, switch puede ser más eficiente que una cadena de if-else.
// Ejemplo de buena práctica con switch mejorado y enum
enum NivelAcceso { INVITADO, USUARIO, EDITOR, ADMINISTRADOR }

NivelAcceso nivel = NivelAcceso.EDITOR;

String mensaje = switch (nivel) {
    case INVITADO -> "Acceso limitado a lectura";
    case USUARIO -> "Acceso a funciones básicas";
    case EDITOR -> "Puede modificar contenido";
    case ADMINISTRADOR -> "Acceso completo al sistema";
};

System.out.println("Tu nivel de acceso: " + mensaje);

Control iterativo con for

Las estructuras de control iterativas, también conocidas como bucles, permiten ejecutar un bloque de código repetidamente mientras se cumpla una condición determinada. El bucle for es una de las estructuras de iteración más utilizadas en Java, especialmente cuando se conoce de antemano el número de iteraciones a realizar.

Sintaxis básica del bucle for

La estructura básica del bucle for consta de tres partes principales:

for (inicialización; condición; actualización) {
    // Código a ejecutar en cada iteración
}
  • Inicialización: Se ejecuta una sola vez al principio y generalmente se utiliza para declarar e inicializar una variable de control.
  • Condición: Se evalúa antes de cada iteración. Si es true, se ejecuta el bloque de código; si es false, el bucle termina.
  • Actualización: Se ejecuta después de cada iteración y normalmente se utiliza para modificar la variable de control.

Veamos un ejemplo básico:

for (int i = 0; i < 5; i++) {
    System.out.println("Iteración número: " + i);
}

En este ejemplo:
1. Se inicializa i con el valor 0

2. Se verifica si i es menor que 5

3. Si la condición es verdadera, se ejecuta el bloque de código

4. Después de la ejecución, se incrementa i en 1

5. Se vuelve al paso 2 y se repite el proceso hasta que i ya no sea menor que 5

La salida sería:

Iteración número: 0
Iteración número: 1
Iteración número: 2
Iteración número: 3
Iteración número: 4

Variaciones del bucle for

Decrementos en lugar de incrementos

Se puede iterar en orden descendente utilizando decrementos:

for (int i = 10; i > 0; i--) {
    System.out.println("Cuenta regresiva: " + i);
}
System.out.println("¡Despegue!");

Incrementos/decrementos diferentes a 1

No es necesario incrementar o decrementar de uno en uno:

// Mostrar los números pares del 0 al 20
for (int i = 0; i <= 20; i += 2) {
    System.out.print(i + " ");
}
// Salida: 0 2 4 6 8 10 12 14 16 18 20

Múltiples variables de control

Se pueden utilizar múltiples variables en un bucle for:

for (int i = 1, j = 10; i <= 10 && j >= 1; i++, j--) {
    System.out.println("i = " + i + ", j = " + j);
}

En este ejemplo, i se incrementa mientras que j se decrementa en cada iteración.

Omitir partes del for

Cualquiera de las tres partes del bucle for puede omitirse, aunque los puntos y comas son obligatorios:

// Inicialización fuera del bucle
int i = 0;
for (; i < 5; i++) {
    System.out.println(i);
}

// Actualización dentro del bloque
for (int j = 0; j < 5;) {
    System.out.println(j);
    j++;
}

// Bucle infinito (la condición siempre es verdadera)
for (;;) {
    System.out.println("Bucle infinito");
    // Necesita una condición de salida interna
    break; // Sale del bucle
}

Bucle for-each (enhanced for)

Java ofrece una versión mejorada del bucle for, conocida como bucle for-each, que simplifica la iteración sobre colecciones y arrays:

for (tipo nombreVariable : colección) {
    // Código a ejecutar para cada elemento
}

Este tipo de bucle es más conciso y menos propenso a errores cuando solo se necesita acceder a los elementos sin modificar la colección:

String[] frutas = {"Manzana", "Plátano", "Naranja", "Fresa"};

// Bucle for tradicional
for (int i = 0; i < frutas.length; i++) {
    System.out.println("Me gusta la " + frutas[i]);
}

// Bucle for-each (más simple)
for (String fruta : frutas) {
    System.out.println("Me gusta la " + fruta);
}

El bucle for-each es especialmente útil con colecciones como ArrayList, Set, etc.:

import java.util.List;
import java.util.ArrayList;

List<Integer> números = new ArrayList<>();
números.add(1);
números.add(2);
números.add(3);

for (Integer número : números) {
    System.out.println("Número: " + número);
}

Limitaciones del bucle for-each

Aunque el bucle for-each es más simple, tiene algunas limitaciones:

  • No permite modificar la colección durante la iteración
  • No proporciona acceso al índice del elemento actual
  • No permite iterar en orden inverso fácilmente
  • No permite iterar sobre múltiples colecciones simultáneamente
// Si necesitas el índice, usa el for tradicional
String[] nombres = {"Ana", "Carlos", "Elena"};
for (int i = 0; i < nombres.length; i++) {
    System.out.println("Posición " + i + ": " + nombres[i]);
}

Control de flujo dentro de bucles

Dentro de cualquier bucle, incluido el for, se pueden utilizar las instrucciones break y continue para controlar el flujo de ejecución:

  • break: Termina el bucle inmediatamente y continúa con el código después del bucle.
  • continue: Salta a la siguiente iteración del bucle, omitiendo el resto del código en la iteración actual.
// Uso de break
for (int i = 1; i <= 10; i++) {
    if (i == 5) {
        System.out.println("Encontrado el 5, saliendo del bucle");
        break;
    }
    System.out.println("Número: " + i);
}

// Uso de continue
for (int i = 1; i <= 10; i++) {
    if (i % 2 == 0) {
        // Salta los números pares
        continue;
    }
    System.out.println("Número impar: " + i);
}

Bucles anidados

Se pueden anidar bucles for para trabajar con estructuras de datos multidimensionales o para resolver problemas más complejos:

// Tabla de multiplicar
for (int i = 1; i <= 10; i++) {
    for (int j = 1; j <= 10; j++) {
        System.out.printf("%4d", i * j);
    }
    System.out.println(); // Nueva línea después de cada fila
}

Este código genera una tabla de multiplicar del 1 al 10. El bucle externo controla las filas, mientras que el bucle interno controla las columnas.

Etiquetas en bucles

Java permite etiquetar bucles, lo que es útil cuando se trabaja con bucles anidados y se necesita salir de un bucle específico:

externo: for (int i = 0; i < 5; i++) {
    interno: for (int j = 0; j < 5; j++) {
        if (i * j > 6) {
            System.out.println("Saliendo del bucle externo cuando i=" + i + ", j=" + j);
            break externo; // Sale del bucle etiquetado como "externo"
        }
        System.out.println("i=" + i + ", j=" + j);
    }
}

En este ejemplo, cuando el producto i * j supera 6, se utiliza break externo para salir completamente del bucle externo, no solo del bucle interno.

Casos de uso comunes

Iteración sobre arrays

int[] números = {10, 20, 30, 40, 50};
int suma = 0;

for (int número : números) {
    suma += número;
}

System.out.println("La suma es: " + suma);

Generación de patrones

// Patrón de triángulo
for (int i = 1; i <= 5; i++) {
    for (int j = 1; j <= i; j++) {
        System.out.print("* ");
    }
    System.out.println();
}

Salida:

* 
* * 
* * * 
* * * * 
* * * * * 

Búsqueda en arrays

int[] valores = {15, 7, 23, 9, 4, 12};
int buscar = 9;
boolean encontrado = false;

for (int i = 0; i < valores.length; i++) {
    if (valores[i] == buscar) {
        System.out.println("Valor encontrado en la posición: " + i);
        encontrado = true;
        break;
    }
}

if (!encontrado) {
    System.out.println("Valor no encontrado");
}

Buenas prácticas

  • Nombres descriptivos: Utilizar nombres significativos para las variables de control.
  • Simplicidad: Mantener el cuerpo del bucle lo más simple posible.
  • Evitar modificaciones: No modificar la variable de control dentro del cuerpo del bucle.
  • Preferir for-each: Usar el bucle for-each cuando sea posible para mayor claridad.
  • Verificar límites: Asegurarse de que los índices no excedan los límites del array.
// Mal ejemplo
for (int i = 0; i < array.length; i++) {
    // Modificar i dentro del bucle puede causar comportamientos inesperados
    if (condición) i += 2;
}

// Buen ejemplo
for (int índice = 0; índice < array.length; índice++) {
    // Cuerpo del bucle sin modificar índice
}

Control iterativo con while y do while

Los bucles while y do-while son estructuras de control iterativas que permiten ejecutar un bloque de código repetidamente mientras se cumpla una condición específica. A diferencia del bucle for, que se utiliza principalmente cuando se conoce el número de iteraciones de antemano, los bucles while y do-while son más adecuados cuando el número de iteraciones depende de una condición que puede cambiar durante la ejecución.

Bucle while

El bucle while evalúa una condición antes de ejecutar el bloque de código. Si la condición es verdadera, se ejecuta el bloque y luego se vuelve a evaluar la condición. Este proceso se repite hasta que la condición se vuelve falsa.

La sintaxis básica es:

while (condición) {
    // Código a ejecutar mientras la condición sea verdadera
}

Veamos un ejemplo sencillo:

int contador = 1;

while (contador <= 5) {
    System.out.println("Contador: " + contador);
    contador++;
}

En este ejemplo:
1. Se inicializa contador con el valor 1

2. Se verifica si contador es menor o igual a 5

3. Si la condición es verdadera, se ejecuta el bloque de código

4. Se incrementa contador en 1

5. Se vuelve al paso 2 y se repite el proceso hasta que contador ya no sea menor o igual a 5

La salida sería:

Contador: 1
Contador: 2
Contador: 3
Contador: 4
Contador: 5

Características importantes del bucle while

  • La condición se evalúa antes de cada iteración
  • Si la condición es falsa desde el principio, el bloque de código no se ejecuta ni una sola vez
  • Es necesario modificar alguna variable dentro del bucle para que la condición eventualmente se vuelva falsa, de lo contrario se crea un bucle infinito
// Ejemplo de bucle infinito (¡cuidado!)
while (true) {
    System.out.println("Este mensaje se imprimirá indefinidamente");
    // Necesita una condición de salida interna
    if (algúnaCriterio) {
        break; // Sale del bucle
    }
}

Bucle do-while

El bucle do-while es similar al while, pero con una diferencia crucial: la condición se evalúa después de ejecutar el bloque de código, lo que garantiza que el bloque se ejecute al menos una vez, incluso si la condición es falsa desde el principio.

La sintaxis básica es:

do {
    // Código a ejecutar al menos una vez
} while (condición);

Veamos un ejemplo:

int contador = 1;

do {
    System.out.println("Contador: " + contador);
    contador++;
} while (contador <= 5);

La salida sería la misma que en el ejemplo anterior:

Contador: 1
Contador: 2
Contador: 3
Contador: 4
Contador: 5

Pero observemos qué sucede si la condición es falsa desde el principio:

int contador = 10;

// Bucle while
while (contador <= 5) {
    System.out.println("Este mensaje nunca se imprimirá");
    contador++;
}

// Bucle do-while
do {
    System.out.println("Este mensaje se imprimirá una vez");
    contador++;
} while (contador <= 5);

En este caso, el bucle while no se ejecuta ni una sola vez porque la condición es falsa desde el principio. Sin embargo, el bucle do-while se ejecuta una vez antes de evaluar la condición.

Casos de uso para while

El bucle while es especialmente útil en situaciones donde:

  • No se conoce el número exacto de iteraciones de antemano
  • La iteración depende de una condición externa o de entrada del usuario
  • Se necesita procesar datos hasta encontrar un valor específico
// Leer entrada del usuario hasta que ingrese "salir"
Scanner scanner = new Scanner(System.in);
String entrada = "";

while (!entrada.equalsIgnoreCase("salir")) {
    System.out.print("Ingrese un comando (o 'salir' para terminar): ");
    entrada = scanner.nextLine();
    
    if (!entrada.equalsIgnoreCase("salir")) {
        System.out.println("Procesando: " + entrada);
    }
}

System.out.println("Programa terminado");

Casos de uso para do-while

El bucle do-while es ideal cuando:

  • Se necesita ejecutar el código al menos una vez, independientemente de la condición
  • Se valida la entrada del usuario después de solicitarla
  • Se implementan menús de opciones que deben mostrarse al menos una vez
// Menú de opciones
Scanner scanner = new Scanner(System.in);
int opción;

do {
    System.out.println("\n--- MENÚ PRINCIPAL ---");
    System.out.println("1. Ver datos");
    System.out.println("2. Agregar nuevo registro");
    System.out.println("3. Modificar registro");
    System.out.println("4. Eliminar registro");
    System.out.println("0. Salir");
    System.out.print("Seleccione una opción: ");
    
    opción = scanner.nextInt();
    
    switch (opción) {
        case 1 -> System.out.println("Mostrando datos...");
        case 2 -> System.out.println("Agregando nuevo registro...");
        case 3 -> System.out.println("Modificando registro...");
        case 4 -> System.out.println("Eliminando registro...");
        case 0 -> System.out.println("Saliendo del programa...");
        default -> System.out.println("Opción no válida, intente de nuevo");
    }
} while (opción != 0);

Control de flujo dentro de bucles while y do-while

Al igual que en el bucle for, se pueden utilizar las instrucciones break y continue para controlar el flujo de ejecución:

int i = 1;

while (i <= 10) {
    if (i == 5) {
        System.out.println("Saltando el número 5");
        i++;
        continue; // Salta a la siguiente iteración
    }
    
    if (i == 8) {
        System.out.println("Encontrado el número 8, saliendo del bucle");
        break; // Sale del bucle
    }
    
    System.out.println("Número: " + i);
    i++;
}

Bucles anidados

También se pueden anidar bucles while y do-while, al igual que los bucles for:

int i = 1;

while (i <= 3) {
    int j = 1;
    while (j <= 3) {
        System.out.print("(" + i + "," + j + ") ");
        j++;
    }
    System.out.println(); // Nueva línea después de cada fila
    i++;
}

Salida:

(1,1) (1,2) (1,3) 
(2,1) (2,2) (2,3) 
(3,1) (3,2) (3,3) 

Comparación entre while, do-while y for

Cada tipo de bucle tiene sus propias ventajas y casos de uso ideales:

Bucle for: Ideal cuando se conoce el número de iteraciones de antemano.

Bucle while: Mejor cuando la condición de terminación depende de factores que pueden cambiar durante la ejecución.

Bucle do-while: Preferible cuando el bloque debe ejecutarse al menos una vez antes de evaluar la condición.

Ejemplos prácticos

Validación de entrada con do-while

Scanner scanner = new Scanner(System.in);
int número;

do {
    System.out.print("Ingrese un número positivo: ");
    número = scanner.nextInt();
    
    if (número <= 0) {
        System.out.println("Error: El número debe ser positivo");
    }
} while (número <= 0);

System.out.println("Número válido ingresado: " + número);

Búsqueda en una lista con while

List<String> nombres = List.of("Ana", "Carlos", "Elena", "David", "Beatriz");
String buscar = "Elena";
boolean encontrado = false;
int índice = 0;

while (índice < nombres.size() && !encontrado) {
    if (nombres.get(índice).equals(buscar)) {
        encontrado = true;
    } else {
        índice++;
    }
}

if (encontrado) {
    System.out.println("Nombre encontrado en la posición: " + índice);
} else {
    System.out.println("Nombre no encontrado");
}

Algoritmo de adivinanza con while

Scanner scanner = new Scanner(System.in);
Random random = new Random();
int númeroSecreto = random.nextInt(100) + 1; // Número entre 1 y 100
int intento;
int intentos = 0;
boolean adivinado = false;

System.out.println("Adivina el número entre 1 y 100");

while (!adivinado) {
    System.out.print("Ingresa tu intento: ");
    intento = scanner.nextInt();
    intentos++;
    
    if (intento < númeroSecreto) {
        System.out.println("El número es mayor");
    } else if (intento > númeroSecreto) {
        System.out.println("El número es menor");
    } else {
        adivinado = true;
        System.out.println("¡Correcto! Has adivinado en " + intentos + " intentos");
    }
}

Buenas prácticas

  • Evitar bucles infinitos: Asegurarse de que la condición eventualmente se vuelva falsa.
  • Inicialización adecuada: Inicializar las variables antes de usarlas en la condición del bucle.
  • Actualización clara: Actualizar las variables de control de manera clara y consistente.
  • Condiciones simples: Mantener las condiciones del bucle lo más simples posible.
  • Elegir el bucle adecuado: Utilizar for cuando se conoce el número de iteraciones, while cuando depende de una condición, y do-while cuando el bloque debe ejecutarse al menos una vez.
// Mal ejemplo: Posible bucle infinito
int contador = 0;
while (contador < 10) {
    System.out.println(contador);
    // Se olvidó incrementar contador
}

// Buen ejemplo
int contador = 0;
while (contador < 10) {
    System.out.println(contador);
    contador++; // Asegura que el bucle terminará
}

Consideraciones de rendimiento

  • Los bucles while y do-while pueden ser ligeramente más eficientes que el bucle for cuando la condición de terminación es simple y no requiere inicialización o actualización adicional.
  • Sin embargo, la diferencia de rendimiento suele ser insignificante en la mayoría de los casos, por lo que la elección del tipo de bucle debe basarse principalmente en la claridad y la adecuación al problema.
// Comparación de rendimiento (diferencia mínima)
long inicio, fin;

// Bucle for
inicio = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
    // Operación
}
fin = System.nanoTime();
System.out.println("Tiempo con for: " + (fin - inicio) + " ns");

// Bucle while
inicio = System.nanoTime();
int j = 0;
while (j < 1000000) {
    // Misma operación
    j++;
}
fin = System.nanoTime();
System.out.println("Tiempo con while: " + (fin - inicio) + " ns");
do {
// Solicitar entrada
// Procesar entrada
} while (!entradaVálida);
while (!condiciónDeSalida) {
// Procesar algo
// Actualizar condiciónDeSalida
}
for (int i = 0; i < 10; i++) {
System.out.println(i);
}

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

Plan mensual

19.00 € /mes

Precio normal mensual: 19 €
47 % DE DESCUENTO

Plan anual

10.00 € /mes

Ahorras 108 € al año
Precio normal anual: 120 €
Aprende Java online

Ejercicios de esta lección Estructuras de control

Evalúa tus conocimientos de esta lección Estructuras de control con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Clases abstractas

Test

Listas

Código

Métodos de la clase String

Código

Streams: reduce()

Test

Polimorfismo

Código

Pattern Matching

Código

Streams: flatMap()

Test

Llamada y sobrecarga de funciones

Puzzle

Métodos referenciados

Test

Métodos de la clase String

Código

Representación de Fecha

Puzzle

Operadores lógicos

Test

Inferencia de tipos con var

Código

Tipos de datos

Código

Estructuras de iteración

Puzzle

Streams: forEach()

Test

Objetos

Puzzle

Funciones lambda

Test

Uso de Scanner

Puzzle

CRUD en Java de modelo Customer sobre un ArrayList

Proyecto

Tipos de variables

Puzzle

Streams: collect()

Puzzle

Operadores aritméticos

Puzzle

Arrays y matrices

Código

Clases y objetos

Código

Interfaz funcional Consumer

Test

Interfaces

Código

Enumeraciones Enums

Código

API java.nio 2

Puzzle

API Optional

Test

Interfaz funcional Function

Test

Encapsulación

Test

Interfaces

Código

Uso de API Optional

Puzzle

Representación de Hora

Test

Herencia básica

Test

Clases y objetos

Código

Interfaz funcional Supplier

Puzzle

HashMap

Puzzle

Sobrecarga de métodos

Test

Polimorfismo de tiempo de ejecución

Puzzle

OOP en Java

Proyecto

Sobrecarga de métodos

Código

Clases sealed

Código

Creación de Streams

Test

Records

Código

Encapsulación

Código

Streams: min max

Puzzle

Métodos avanzados de la clase String

Puzzle

Funciones

Código

Polimorfismo de tiempo de compilación

Test

Reto sintaxis Java

Proyecto

Conjuntos

Código

Estructuras de control

Código

Recursión

Código

Excepciones

Puzzle

Herencia avanzada

Puzzle

Estructuras de selección

Test

Uso de interfaces

Test

Operadores

Código

Variables

Código

HashSet

Test

Objeto Scanner

Test

Streams: filter()

Puzzle

Operaciones de Streams

Puzzle

Interfaz funcional Predicate

Puzzle

Streams: sorted()

Test

Configuración de entorno

Test

CRUD en Java de modelo Customer sobre un HashMap

Proyecto

Uso de variables

Test

Clases

Test

Streams: distinct()

Puzzle

Streams: count()

Test

ArrayList

Test

Datos de referencia

Test

Interfaces funcionales

Puzzle

Métodos básicos de la clase String

Test

Tipos de datos

Código

Clases abstractas

Código

Instalación

Test

Funciones

Código

Excepciones

Código

Estructuras de control

Código

Herencia de clases

Código

La clase Scanner

Código

Generics

Código

Streams: map()

Puzzle

Funciones y encapsulamiento

Test

Streams: match

Test

Gestión de errores y excepciones

Código

Datos primitivos

Puzzle

Todas las lecciones de Java

Accede a todas las lecciones de Java y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Instalación De Java

Introducción Y Entorno

Configuración De Entorno Java

Introducción Y Entorno

Tipos De Datos

Sintaxis

Variables

Sintaxis

Operadores

Sintaxis

Estructuras De Control

Sintaxis

Funciones

Sintaxis

Recursión

Sintaxis

Excepciones

Programación Orientada A Objetos

Clases Y Objetos

Programación Orientada A Objetos

Encapsulación

Programación Orientada A Objetos

Herencia

Programación Orientada A Objetos

Clases Abstractas

Programación Orientada A Objetos

Interfaces

Programación Orientada A Objetos

Sobrecarga De Métodos

Programación Orientada A Objetos

Polimorfismo

Programación Orientada A Objetos

La Clase Scanner

Programación Orientada A Objetos

Métodos De La Clase String

Programación Orientada A Objetos

Records

Programación Orientada A Objetos

Pattern Matching

Programación Orientada A Objetos

Inferencia De Tipos Con Var

Programación Orientada A Objetos

Enumeraciones Enums

Programación Orientada A Objetos

Generics

Programación Orientada A Objetos

Clases Sealed

Programación Orientada A Objetos

Listas

Framework Collections

Conjuntos

Framework Collections

Mapas

Framework Collections

Funciones Lambda

Programación Funcional

Interfaz Funcional Consumer

Programación Funcional

Interfaz Funcional Predicate

Programación Funcional

Interfaz Funcional Supplier

Programación Funcional

Interfaz Funcional Function

Programación Funcional

Métodos Referenciados

Programación Funcional

Creación De Streams

Programación Funcional

Operaciones Intermedias Con Streams: Map()

Programación Funcional

Operaciones Intermedias Con Streams: Filter()

Programación Funcional

Operaciones Intermedias Con Streams: Distinct()

Programación Funcional

Operaciones Finales Con Streams: Collect()

Programación Funcional

Operaciones Finales Con Streams: Min Max

Programación Funcional

Operaciones Intermedias Con Streams: Flatmap()

Programación Funcional

Operaciones Intermedias Con Streams: Sorted()

Programación Funcional

Operaciones Finales Con Streams: Reduce()

Programación Funcional

Operaciones Finales Con Streams: Foreach()

Programación Funcional

Operaciones Finales Con Streams: Count()

Programación Funcional

Operaciones Finales Con Streams: Match

Programación Funcional

Api Optional

Programación Funcional

Api Java.nio 2

Entrada Y Salida (Io)

Api Java.time

Api Java.time

Ecosistema Jakarta Ee De Java

Frameworks Para Java

Accede GRATIS a Java y certifícate

Certificados de superación de Java

Supera todos los ejercicios de programación del curso de Java y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la estructura básica de una sentencia if
  • Aprender a emplear else if y else para manejar múltiples condiciones
  • Saber cuándo usar operadores lógicos dentro de las condiciones
  • Conocer el uso del operador ternario y sus ventajas
  • Aplicar buenas prácticas para mejorar la claridad y eficiencia del código