Skip to content

Programación Estructurada

¡Muy buenas, equipo! En el tema anterior sentamos las bases de nuestro rascacielos. Aprendimos qué es un algoritmo (el plano), un programa (el edificio) y los distintos materiales de construcción (los lenguajes). Ahora toca ponerse el casco y empezar a levantar muros. Hoy vamos a ver las dos filosofías de construcción que dominan el mundo del software: la Programación Estructurada.

Imaginad que estáis montando un PC gaming de última generación. La programación estructurada es como el manual de instrucciones: una serie de pasos claros y ordenados. "Primero, instala la CPU en la placa base. Segundo, si tienes dos módulos de RAM, colócalos en los slots A2 y B2. Tercero, repite para cada tornillo: apriétalo hasta que haga tope". Son secuencias, decisiones y repeticiones. Sin estas tres estructuras, el montaje sería un caos de cables y componentes sin sentido.

Pero, ¿qué pasa con los componentes en sí? El procesador, la tarjeta gráfica, la fuente de alimentación... cada uno es una pequeña maravilla de la ingeniería, diseñado por un equipo de expertos para hacer una cosa y hacerla bien. Podéis comprar una tarjeta gráfica NVIDIA o AMD, y ambas funcionarán si respetan el estándar PCIe. Eso, compañeros, es la programación modular. En lugar de construir el PC átomo por átomo, lo montamos a partir de "módulos" (componentes) ya funcionales y reutilizables.

En este tema, aprenderemos a usar las herramientas de la programación estructurada (los if, for, while) para dar lógica a nuestro código, y luego en el siguiente veremos cómo la programación modular (creando nuestras propias funciones y procedimientos) nos permite construir software más limpio, reutilizable y fácil de mantener, como si estuviéramos montando un PC con piezas de alta gama. ¡Manos a la obra!

1. Tipos y Operaciones

1.1. Almacenamiento de Datos

Antes de profundizar en las variables en Java, es fundamental comprender cómo se guarda la información en la memoria de los dispositivos.

En informática, todos los datos se almacenan en formato binario, utilizando solo dos valores: 0 y 1. La unidad más pequeña de información se llama bit, que puede contener únicamente un 0 o un 1. Sin embargo, cuando hablamos de capacidad de almacenamiento, solemos referirnos a bytes, donde 1 byte equivale a 8 bits.

La cantidad de bits utilizados determina el número de combinaciones posibles, lo que permite almacenar diversos tipos de valores. Por ejemplo:

Almacenamiento

Almacenamiento Combinaciones

Los tipos de variables numéricas disponibles en JAVA son los siguientes:

Tipos Numéricos

Representación interna del número 3 en cada uno de los tipos de datos enteros: Almacenar Valor

Otros Tipos de Variables Básicas

Existen varios tipos de variables básicas en Java que no están destinadas a datos numéricos:

  • char: Permite almacenar un solo carácter, como una letra, un número o cualquier otro símbolo alfanumérico. Utiliza 2 bytes de memoria y se basa en el estándar Unicode, que incluye el estándar anterior ASCII.
  • boolean: Sirve para evaluar condiciones y solo puede tomar dos valores: true (verdadero) o false (falso). Ocupa 1 bit de memoria.
  • String: Se emplea para guardar secuencias de caracteres, es decir, múltiples valores de tipo char. Aunque no es un tipo de dato primitivo, es ampliamente utilizado.

Antes de poder construir nada, necesitamos entender nuestros materiales. En programación, esos materiales son los datos, y para que el ordenador sepa cómo tratarlos, los clasificamos en tipos. Un tipo de dato no es más que una etiqueta que le dice a JAVA: "Oye, en esta caja solo puedes guardar números enteros" o "en esta otra, solo texto". Esto evita desastres como intentar sumar tu nombre a tu edad.

Definición: Tipo de Dato

Un tipo de dato define dos cosas: 1. El conjunto de valores que una variable puede almacenar (ej: números enteros, texto, valores de verdad). 2. El conjunto de operaciones que se pueden realizar con esos valores (ej: puedes sumar dos números, pero no puedes dividir dos nombres).

En JAVA, como en la mayoría de los lenguajes, distinguimos entre tipos simples (o primitivos) y tipos compuestos.

  • Tipos Simples (Primitivos): Son los ladrillos más básicos. Representan un único valor.
  • Tipos Compuestos (de Objeto o Referencia): Son estructuras más complejas construidas a partir de los tipos simples. No almacenan el dato directamente, sino una "dirección" o referencia a donde se encuentra el dato en memoria. String, los arrays, y cualquier clase que creemos, son de este tipo.

Declaración de Variables en Java

Para declarar una variable en Java, primero se especifica el tipo de dato que la variable podrá contener, seguido del nombre (identificador) que se le asignará. Opcionalmente, se puede asignar un valor inicial utilizando el signo igual. La declaración debe concluir con un punto y coma.

Declaración de variable

¿Cómo Funciona la Declaración y Asignación de Variables? El proceso de declarar y asignar valores a variables en Java se realiza así:

Se calcula el valor de la expresión ubicada a la derecha del signo igual. El resultado de esa expresión se almacena en la variable situada a la izquierda del igual.

Además, es posible asignar a una variable el valor de otra variable o realizar operaciones entre ellas. Ejemplo:

int cantidad = 5 + 2; // Almacena 7 en cantidad
int precio = 10; // Almacena 10 en precio
precio = 5; // Cambia el valor de precio a 5
int precioFinal = cantidad * precio; // Almacena 35 en precioFinal
precioFinal = precioFinal + 10; // Actualiza precioFinal a 45

Identificador de Variables El identificador de una variable es el nombre que se le asigna. Debe cumplir con las siguientes normas:

Tiene que iniciar con una letra, un guion bajo (_) o el símbolo $. No puede comenzar con un número, aunque sí se pueden incluir números después del primer carácter. Los identificadores son sensibles a mayúsculas y minúsculas (case-sensitive), es decir, distinguen entre letras mayúsculas y minúsculas. No se permiten espacios en blanco ni símbolos de operadores. No deben coincidir con ninguna palabra reservada de Java. El símbolo % no está permitido, pero sí se aceptan el $ y la ç. Aunque es posible usar acentos y la ñ, no se recomienda hacerlo.

Además hay palabras reservadas que no se deben utilizar como identificadores para las variables:

Declaración de variable

Ejemplo de Tipos de Datos

// Tipos de datos en JAVA

// --- Tipos Primitivos (Simples) ---

// Números enteros
byte unByte = 127;           // 8 bits, de -128 a 127
short unShort = 32767;       // 16 bits
int unEntero = 2147483647;   // 32 bits (el más común para enteros)
long unLargo = 9223372036854775807L; // 64 bits, la 'L' al final es obligatoria

// Números de punto flotante (decimales)
float unFlotante = 3.14159f; // 32 bits, la 'f' al final es obligatoria
double unDoble = 3.141592653589793; // 64 bits (el estándar para decimales)

// Caracteres
char unCaracter = 'A';       // 16 bits, un único carácter Unicode. Se usan comillas simples.
char otroCaracter = '\u00A9'; // El símbolo de copyright (©)

// Lógicos
boolean esEstudiante = true; // Solo puede ser true o false

// --- Tipos Compuestos (de Referencia/Objeto) ---

// Cadenas de texto (el más usado)
String unNombre = "Ada Lovelace"; // Objeto que representa una secuencia de caracteres. Comillas dobles.

// Arrays (colecciones de un mismo tipo)
int[] notasExamenes = {10, 8, 5, 7, 9};
String[] listaCompra = {"Leche", "Pan", "Huevos"};

// Matrices (array de arrays)
int[][] tableroTresEnRaya = {
    {1, 0, -1},
    {1, -1, 0},
    {0, 1, -1}
};

1.2. Lenguajes tipados y no tipados

Aquí entramos en una de las grandes batallas filosóficas de la programación. JAVA pertenece a un bando muy claro.

  • Lenguajes Fuertemente Tipados (o de Tipado Estático): JAVA, C++, C#, Kotlin. Son como un inspector de obras muy estricto. Te obligan a declarar el tipo de cada variable antes de usarla (int edad;) y comprueban en tiempo de compilación que no intentes hacer cosas raras, como edad = "dieciocho";. Si lo intentas, el programa ni siquiera se genera. Esto pilla muchos errores de antemano y hace el código más seguro y, a menudo, más rápido.

  • Lenguajes Débilmente Tipados (o de Tipado Dinámico): Python, JavaScript, PHP. Son más relajados. No te obligan a declarar el tipo. Una variable puede contener un número en una línea y un texto en la siguiente. El tipo se decide en tiempo de ejecución. Esto da mucha flexibilidad y permite escribir código más rápido, pero también es una fuente común de errores que solo aparecen cuando el programa ya está funcionando.

sequenceDiagram
    participant Programador
    participant Compilador as Compilador (JAVA)
    participant Interprete as Intérprete (Python)

    Programador->>Compilador: Escribe: int edad = "hola";
    Compilador-->>Programador: ¡ERROR! No puedes hacer eso. El programa no se compila.

    Programador->>Interprete: Escribe: edad = "hola"
    Interprete->>Interprete: El programa se ejecuta...
    Note right of Interprete: ...hasta que intentas hacer:<br>edad + 5
    Interprete-->>Programador: ¡ERROR en ejecución! No puedo sumar texto y número.

1.3. Operaciones

Las operaciones son las acciones que podemos realizar con nuestros datos. Se representan mediante operadores.

// --- Operadores Aritméticos ---
int suma = 10 + 5;            // Suma: 15
int resta = 10 - 5;            // Resta: 5
int multiplicacion = 10 * 5;   // Multiplicación: 50
int divisionEntera = 10 / 3;   // División entera: 3 (se trunca la parte decimal)
double divisionReal = 10.0 / 3.0; // División real: 3.333...
int modulo = 10 % 3;           // Módulo (resto de la división): 1

// --- Operadores de Asignación ---
int valor = 10;
valor += 5; // Suma y asignación (equivale a valor = valor + 5;). Ahora es 15.
valor -= 3; // Resta y asignación. Ahora es 12.
valor *= 2; // Multiplicación y asignación. Ahora es 24.
valor /= 4; // División y asignación. Ahora es 6.

Operadores de asignación

int contador = 0;
contador++;                    // Incremento postfijo (ahora contador es 1)
++contador;                    // Incremento prefijo (ahora contador es 2)
contador--;                    // Decremento postfijo (ahora contador es 1)
--contador;                    // Decremento prefijo (ahora contador es 0)

Operadores de incremento

// --- Operadores Relacionales (devuelven boolean) ---
boolean esIgual = (5 == 5);     // Igualdad: true
boolean esDistinto = (5 != 3);  // Desigualdad: true
boolean esMayor = (5 > 3);      // Mayor que: true
boolean esMenor = (5 < 3);      // Menor que: false
boolean esMayorOIgual = (5 >= 5); // Mayor o igual que: true
boolean esMenorOIgual = (5 <= 3); // Menor o igual que: false

// --- Operadores Lógicos (operan sobre booleans) ---
boolean tieneCarnet = true;
boolean esMayorDeEdad = true;
boolean puedeConducir = tieneCarnet && esMayorDeEdad; // AND (Y lógico): true

boolean tieneDescuentoJoven = true;
boolean tieneDescuentoFamilia = false;
boolean aplicaDescuento = tieneDescuentoJoven || tieneDescuentoFamilia; // OR (O lógico): true

boolean estaLloviendo = true;
boolean noLlueve = !estaLloviendo; // NOT (Negación): false

Operadores de incremento

Operadores de incremento

Reflexión

  1. Declara en JAVA una variable para almacenar el precio de un producto, otra para el nombre de un videojuego y otra para saber si un usuario está o no conectado.
  2. Escribe una expresión en JAVA que sea true si un alumno ha aprobado (nota >= 5) y además ha entregado todos los trabajos (haEntregadoTodo == true).
  3. ¿Qué resultado daría la operación 13 % 5 en JAVA? ¿Y 13 / 5? ¿Por qué son diferentes?



1.4. Precedencia de Operadores

No todas las operaciones se realizan en el mismo orden. Al igual que en matemáticas, JAVA tiene unas reglas de prioridad para saber qué calcular primero. Si escribes 3 + 5 * 2, JAVA no calcula 8 * 2, sino que sabe que la multiplicación va primero, calculando 3 + 10.

Tabla de Precedencia (Simplificada para JAVA)

Nivel Categoría Operadores Asociatividad
1 Postfijo expr++, expr-- Izquierda a derecha
2 Unario ++expr, --expr, +, -, ! Derecha a izquierda
3 Multiplicativo *, /, % Izquierda a derecha
4 Aditivo +, - Izquierda a derecha
5 Relacional <, >, <=, >= Izquierda a derecha
6 Igualdad ==, != Izquierda a derecha
7 AND Lógico && Izquierda a derecha
8 OR Lógico || Izquierda a derecha
9 Asignación =, +=, -=, *=, /=, %= Derecha a izquierda

Regla de oro: ante la duda, usa paréntesis

Aunque es bueno conocer la precedencia, el código más legible es el que no te hace pensar. Usar paréntesis () para agrupar operaciones no solo fuerza el orden que tú quieres, sino que hace el código infinitamente más fácil de leer para otros (y para tu "yo" del futuro).

Ejemplos Prácticos

Expresión Completa:

public class Precedencia {
    public static void main(String[] args) {
        boolean resultado = 5 + 3 * 2 < 15 && (10 % 3 == 1 || 8 > 10);
        // Desglose paso a paso:
        // 1. Paréntesis: (10 % 3 == 1 || 8 > 10)
        //    a. Dentro del paréntesis, el módulo primero: 10 % 3 -> 1
        //    b. Comparaciones: 1 == 1 -> true;  8 > 10 -> false
        //    c. OR lógico: true || false -> true
        //    Resultado del paréntesis: true
        // 2. Expresión principal: 5 + 3 * 2 < 15 && true
        //    a. Multiplicación: 3 * 2 -> 6
        //    b. Suma: 5 + 6 -> 11
        //    c. Relacional: 11 < 15 -> true
        //    d. AND lógico final: true && true -> true
        // 3. El resultado final es true
        System.out.println("El resultado es: " + resultado); // Imprime true
    }
}

Caso Peligroso: Asignación vs. Comparación

// ¡¡ERROR COMÚN DE NOVATO!!
int x = 5;
if (x = 10) { // Esto no COMPARA, ¡ASIGNA 10 a x y se evalúa como si fuera un boolean (lo cual es un error en JAVA)!
    // Este código da un error de compilación en JAVA,
    // ¡pero en otros lenguajes como C podría causar un bug terrible!
}

// Lo correcto es usar el operador de igualdad '=='
if (x == 10) {
    System.out.println("x vale 10");
}

¡Mucho cuidado con = vs ==!

En JAVA, el compilador suele salvarte de asignar dentro de un if. Pero en otros lenguajes, es una de las fuentes de bugs más difíciles de encontrar. Acostúmbrate desde ya a diferenciar mentalmente asignación (=) de comparación (==).



1.5. Salida de Datos en Java

La salida de datos en Java se gestiona mediante la clase System.out. Esta clase proporciona acceso a los siguientes métodos:

print: Muestra en pantalla una cadena de tipo String. También permite mostrar otros tipos de datos básicos, ya que se convierten automáticamente a su representación en texto. Además, se puede usar el operador + para concatenar cadenas. Los literales de texto deben ir entre comillas dobles, salvo un solo carácter, que también puede ir entre comillas simples. println: Similar a print, pero añade un salto de línea al final.

Ejemplo:

System.out.print("Saludos");
System.out.println("Aquí empieza un salto de línea");

Es posible combinar texto entre comillas con los valores de variables utilizando el operador de suma (+). A continuación, se muestran ejemplos:

int total = 15;
System.out.print("El total es " + total); // Salida: El total es 15
System.out.println("El total es " + total + 3); // Salida: El total es 153
System.out.println("El total es " + (total + 3)); // Salida: El total es 18
System.out.println(total + 3); // Salida: 18

Consideraciones: Es importante incluir espacios en blanco dentro del texto para separar adecuadamente el contenido de las variables. También se pueden concatenar cadenas de texto con el resultado de una expresión, como se muestra en los ejemplos anteriores.

1.6. Casting y Conversión de Tipos

A veces necesitamos convertir un dato de un tipo a otro. Es como intentar meter una moneda en una máquina que solo acepta billetes; necesitas cambiarla primero.

Definición: Casting (Moldeado de tipo)

El casting es el proceso de convertir un valor de un tipo de dato a otro. Puede ser implícito (el compilador lo hace por ti) o explícito (tú fuerzas la conversión).

Casting Implícito (Widening Conversion)

Ocurre automáticamente cuando conviertes un tipo de dato "pequeño" a uno "grande", donde no hay riesgo de perder información.

public class CastingImplicito {
    public static void main(String[] args) {
        int miEntero = 100;
        double miDoble = miEntero; // Conversión implícita de int a double

        System.out.println("Entero: " + miEntero); // 100
        System.out.println("Doble: " + miDoble);   // 100.0
    }
}

Casting Explícito (Narrowing Conversion)

Ocurre cuando conviertes un tipo "grande" a uno "pequeño". Aquí hay riesgo de perder información (como la parte decimal), por lo que debes decirle explícitamente al compilador que estás seguro de lo que haces.

public class CastingExplicito {
    public static void main(String[] args) {
        double miDoble = 9.78;
        // int miEntero = miDoble; // ERROR DE COMPILACIÓN!
        int miEntero = (int) miDoble; // Casting explícito de double a int

        System.out.println("Doble: " + miDoble);     // 9.78
        System.out.println("Entero: " + miEntero);   // 9 (¡se pierde la parte decimal!)
    }
}

¡Peligro con el Casting Explícito!

Cuando haces un casting explícito, asumes la responsabilidad. Si el valor del tipo grande no cabe en el pequeño, puedes obtener resultados inesperados o errores.

Ejemplos en JAVA

public class Conversiones {
    public static void main(String[] args) {
        // --- Conversión Implícita ---
        int miEntero = 100;
        double miDoble = miEntero; // Perfecto, JAVA lo hace solo. miDoble ahora es 100.0
        System.out.println("Implícita: " + miDoble);

        // --- Conversión Explícita (Casting) ---
        double precio = 99.99;
        // int precioEntero = precio; // ¡ERROR DE COMPILACIÓN! No se puede hacer implícitamente.
        int precioEntero = (int) precio; // ¡Correcto! Le decimos a JAVA que lo fuerce.
        System.out.println("Explícita (casting): " + precioEntero); // Imprime 99 (se pierde la parte decimal)

        // --- Conversión con métodos de ayuda ---

        // De String a Número
        String textoNumero = "123";
        int numeroDesdeTexto = Integer.parseInt(textoNumero);
        System.out.println("String a int: " + (numeroDesdeTexto + 1)); // Imprime 124

        // De Número a String
        int puntuacion = 500;
        String textoPuntuacion = String.valueOf(puntuacion);
        System.out.println("Int a String: " + textoPuntuacion.length()); // Imprime 3

        // De char a su código numérico (ASCII/Unicode)
        char letra = 'A';
        int codigoLetra = (int) letra;
        System.out.println("El código de 'A' es: " + codigoLetra); // Imprime 65

        // De código numérico a char
        int codigo = 66;
        char letraDesdeCodigo = (char) codigo;
        System.out.println("El carácter del código 66 es: " + letraDesdeCodigo); // Imprime 'B'
    }
}

Peligros del Casting

Cuando haces un casting explícito, estás asumiendo la responsabilidad. Si el valor "grande" no cabe en el tipo "pequeño", puedes obtener resultados inesperados o erróneos sin que el programa te avise.

int numeroGrande = 130;
byte numeroPequeno = (byte) numeroGrande; // byte va de -128 a 127
System.out.println(numeroPequeno); // Imprime -126. ¡Un resultado totalmente inesperado!



1.7. Variables y Constantes

Ya hemos usado variables, pero es hora de formalizarlo. En el mundo del desarrollo, es crucial distinguir entre datos que van a cambiar y datos que deben permanecer fijos.

Definición: Variable

Una variable es un espacio en memoria que tiene un nombre y almacena un valor que puede cambiar durante la ejecución del programa. Se declara simplemente con su tipo y nombre.

Definición: Constante

Una constante es una variable cuyo valor no puede ser modificado una vez que ha sido asignado. En JAVA, se declaran usando la palabra clave final. Por convención, sus nombres se escriben en mayúsculas (SNAKE_CASE).

La importancia de las constantes

Usar constantes hace tu código más legible (es mejor leer precio * IVA que precio * 0.21) y más fácil de mantener (si el IVA cambia, solo lo modificas en un sitio).

public class VariablesYConstantes {

    // Una constante de clase. `static` significa que pertenece a la clase, no a un objeto.
    // `final` significa que su valor no puede cambiar.
    // Por convención, los nombres de las constantes se escriben en MAYÚSCULAS.
    static final double PI = 3.14159;
    static final int HORAS_DIA = 24;

    public static void main(String[] args) {
        // Una variable local
        int contador = 0;
        System.out.println("Contador inicial: " + contador);
        contador = contador + 1; // Podemos cambiar su valor
        System.out.println("Contador final: " + contador);

        // Una constante local (variable final)
        final String MENSAJE_BIENVENIDA = "¡Bienvenido a DAW!";
        System.out.println(MENSAJE_BIENVENIDA);
        // MENSAJE_BIENVENIDA = "Adiós"; // ¡ERROR DE COMPILACIÓN! No se puede reasignar una variable final.
    }
}

Reflexión

  1. ¿Cuándo usarías una variable y cuándo una constante para almacenar el número de intentos en un juego?
  2. ¿Por qué es una buena práctica usar nombres en mayúsculas para las constantes?
  3. Imagina que estás programando una tienda online. ¿El precio de un producto sería una variable o una constante? ¿Y el porcentaje de IVA? Razona tu respuesta.



1.8. Operaciones con Strings

Los String en JAVA, aunque parecen un tipo primitivo, son en realidad objetos. Esto les da una gran potencia y un montón de "habilidades" (métodos) incorporadas que usaremos constantemente.

Tabla de Métodos Comunes de String

Método Descripción Ejemplo
length() Devuelve la longitud (número de caracteres) del String. "Hola".length()4
charAt(int index) Devuelve el carácter en la posición especificada. "Java".charAt(0)'J'
substring(int begin, int end) Extrae una subcadena desde begin hasta end-1. "Programación".substring(0, 4)"Progra"
toUpperCase() / toLowerCase() Convierte el String a mayúsculas o minúsculas. "Hola".toUpperCase()"HOLA"
equals(String another) Compara si dos Strings tienen el mismo contenido. "A".equals("a")false
equalsIgnoreCase(String another) Igual que equals, pero ignora mayúsculas/minúsculas. "A".equalsIgnoreCase("a")true
contains(String s) Devuelve true si el String contiene la subcadena. "Hola Mundo".contains("Mundo")true
replace(char old, char new) Reemplaza todas las ocurrencias de un carácter. "casa".replace('a', 'o')"coso"
trim() Elimina los espacios en blanco al principio y al final. " hola ".trim()"hola"
public class OperacionesString {
    public static void main(String[] args) {
        String frase = "  JAVA es Genial!  ";

        // Longitud
        System.out.println("Longitud: " + frase.length()); // 19

        // Quitar espacios y convertir a minúsculas
        String fraseLimpia = frase.trim().toLowerCase();
        System.out.println("Frase limpia: '" + fraseLimpia + "'"); // 'java es genial!'

        // Extraer una parte
        String primeraPalabra = fraseLimpia.substring(0, 4);
        System.out.println("Primera palabra: " + primeraPalabra); // "java"

        // Reemplazar
        String conExclamaciones = fraseLimpia.replace("!", "!!!");
        System.out.println("Con exclamaciones: " + conExclamaciones); // "java es genial!!!"

        // Comparar
        if (primeraPalabra.equals("java")) {
            System.out.println("La primera palabra es 'java'.");
        }
    }
}

NUNCA uses == para comparar Strings

El operador == en objetos compara si dos referencias apuntan al mismo objeto en memoria, no si tienen el mismo contenido. Puede que funcione a veces por optimizaciones del compilador, pero fallará en otros casos. Usa siempre el método .equals() para comparar el contenido de los Strings.

String s1 = new String("hola");
String s2 = new String("hola");

System.out.println(s1 == s2);      // false (son dos objetos diferentes)
System.out.println(s1.equals(s2)); // true (tienen el mismo contenido)

Secuencias de escape

Secuencias de escape

Ejemplos en el uso de secuencias de escape

String primerNombre = "Ana";
String apellidosCompletos = "Gómez López";
String nombreTotal1 = primerNombre + " " + apellidosCompletos; // Ana Gómez López
String nombreTotal2 = "\"+primerNombre+apellidosCompletos+\""; // "+primerNombre+apellidosCompletos+"
String nombreTotal3 = "\"" + primerNombre + apellidosCompletos + "\""; // "AnaGómez López"
String nombreDuplicado = primerNombre + "\b" + primerNombre; // AnAna
String apellidosMod1 = apellidosCompletos + "\r," + primerNombre; // ,Anaópez
String apellidosMod2 = apellidosCompletos + "\b," + primerNombre; // Gómez Lópe,Ana
String apellidosMod3 = apellidosCompletos + "\\," + primerNombre; // Gómez López\,Ana
String apellidosMod4 = "\u00A9 " + primerNombre; // © Ana
String apellidosMod5 = "\u00AE " + primerNombre; // ® Ana
String apellidosMod6 = "\u2605 " + primerNombre; // ★ Ana
String apellidosMod7 = "\u2661 " + primerNombre; // ♡ Ana

Bloques de Texto

Desde la versión 15 de Java, se introdujeron los bloques de texto, que facilitan la creación de cadenas (String) de múltiples líneas. Para definirlos, se utilizan tres comillas dobles consecutivas tanto para abrir como para cerrar la cadena. Es importante que las líneas que contienen las comillas no incluyan ningún otro contenido.

Ejemplo:

String mensaje = """
Este es un texto de varias líneas
Realizando un ejemplo
....
""";

String contenido = """
Este es un texto de varias líneas
Realizando un ejemplo
....
Continúa aquí
""";



1.9. Comentarios

Los comentarios son el "alma" del código. Son notas para humanos que el compilador ignora por completo. Un buen código se explica por sí mismo, pero un buen comentario explica el "porqué".

Comenta el 'porqué', no el 'qué'

Mal comentario (obvio): i++; // Incrementa i en 1Buen comentario (explica la intención): intentos++; // El usuario ha fallado, incrementamos el contador de intentos.

En JAVA tenemos tres tipos de comentarios:

public class TiposDeComentarios {
    // 1. Comentario de una sola línea. Empieza con // y llega hasta el final de la línea.
    // Es perfecto para notas cortas y aclaraciones puntuales.

    /*
     * 2. Comentario de múltiples líneas.
     * Empieza con /* y termina con */
     * Todo lo que esté en medio será ignorado.
     * Es útil para explicaciones más largas o para "desactivar" temporalmente un bloque de código.
     */

    /**
     * 3. Comentario de Documentación (Javadoc).
     * Es un tipo especial de comentario multilínea que empieza con /**
     * Se usa para generar documentación automática de tu código.
     * Es la forma profesional de explicar qué hace una clase o un método.
     * @param args Argumentos de la línea de comandos (no se usan en este ejemplo).
     */
    public static void main(String[] args) {
        System.out.println("Este código sí se ejecuta.");
    }
}

Reflexión

  1. ¿Qué tipo de comentario usarías para explicar brevemente lo que hace una variable compleja?
  2. ¿Y si necesitas desactivar temporalmente 15 líneas de código para hacer una prueba?
  3. ¿Por qué es importante documentar tu código, especialmente si trabajas en equipo?



1.10. Entrada de datos

En Java, podemos capturar datos desde el teclado utilizando la clase Scanner. El método .next() permite leer datos hasta encontrar un espacio en blanco, mientras que el método .nextLine() captura los datos hasta llegar a un salto de línea.

import java.util.Scanner;

class LectorTeclado {
    public static void main(String[] args) {
        String apodo;
        System.out.print("Ingrese su apodo (una palabra): ");
        Scanner lector = new Scanner(System.in); // Creamos un objeto Scanner
        apodo = lector.next(); // Leemos hasta encontrar un espacio en blanco
        System.out.println("¡Hola, " + apodo + "!");
    }
}

Además de leer cadenas de texto, es posible capturar números utilizando los siguientes métodos:

lectorDatos.nextInt(); para leer un número entero.
lectorDatos.nextFloat(); para leer un número decimal de tipo float.
lectorDatos.nextDouble(); para leer un número decimal de tipo double.

Si se desea obtener múltiples datos, se puede emplear el método .hasNext(), que devuelve un valor booleano (true o false) indicando si hay más datos por leer. Este método se usa comúnmente en bucles para procesar entradas repetitivas, como en:

while (lectorDatos.hasNext()) { ... }

Consideraciones al Leer Datos Numéricos y de Texto

Cuando se leen datos numéricos y luego datos de tipo texto (carácter o String) desde el teclado, hay que tener cuidado con el comportamiento del buffer de entrada. Al ingresar un dato y presionar "Enter", se introduce un carácter de salto de línea (\n) en el buffer. Por ejemplo, en la instrucción:

int valor = lectorDatos.nextInt();

Si el usuario ingresa 8, la variable valor toma el valor 8, pero el salto de línea (\n) permanece en el buffer. Esto puede causar problemas si a continuación se intenta leer una cadena de texto con:

System.out.print("Introduzca su ciudad: ");
ciudad = lectorDatos.nextLine();

El método nextLine() lee todos los caracteres del buffer hasta encontrar un salto de línea y lo elimina. En este caso, si el buffer contiene un \n residual, nextLine() lo consumirá, asignando una cadena vacía a ciudad y pasando de largo sin permitir al usuario ingresar el texto. Esto provoca un mal funcionamiento del programa. Ejemplo con Problema Considera el siguiente código:

import java.util.Scanner;
public class Cubo {
    public static void main(String[] args) {
        Scanner lectorDatos = new Scanner(System.in);
        String ciudad;
        int valor;
        System.out.print("Introduzca un número entero: ");
        valor = lectorDatos.nextInt();
        System.out.println("El cubo es: " + Math.pow(valor, 3));
        System.out.print("Introduzca su ciudad: ");
        ciudad = lectorDatos.nextLine(); // Lee una cadena vacía
        System.out.println("Bienvenido desde " + ciudad + "!!");
    }
}

En este ejemplo, después de leer el número entero, el salto de línea queda en el buffer, y nextLine() lo consume, asignando una cadena vacía a ciudad. Como resultado, el programa no se detiene para que el usuario ingrese la ciudad.

Solución

Para evitar este problema, se debe limpiar el buffer de entrada consumiendo el salto de línea residual antes de leer la cadena. Esto se logra llamando a nextLine() adicionalmente después de leer el número. Aquí está el código corregido:

import java.util.Scanner;
public class Cubo {
    public static void main(String[] args) {
        Scanner lectorDatos = new Scanner(System.in);
        String ciudad;
        int valor;
        System.out.print("Introduzca un número entero: ");
        valor = lectorDatos.nextInt();
        lectorDatos.nextLine(); // Limpia el salto de línea del buffer
        System.out.println("El cubo es: " + Math.pow(valor, 3));
        System.out.print("Introduzca su ciudad: ");
        ciudad = lectorDatos.nextLine(); // Lee correctamente la cadena
        System.out.println("Bienvenido desde " + ciudad + "!!");
    }
}

Explicación de la Solución

La línea lectorDatos.nextLine(); después de nextInt() consume el salto de línea residual (\n) del buffer, dejando limpio el buffer para la siguiente lectura con nextLine(). Esto asegura que el programa espere correctamente la entrada de la cadena de texto, como el nombre de la ciudad.



2. Algoritmo

Como vimos en la introducción, un algoritmo es nuestra receta para resolver un problema. Antes de lanzarnos a escribir código en JAVA, es fundamental tener claro el algoritmo. Dos herramientas clásicas para diseñar y visualizar algoritmos son el pseudocódigo y los diagramas de flujo.

Definición: Pseudocódigo

Es una forma de describir un algoritmo usando un lenguaje simple, parecido al lenguaje natural, pero con una estructura que se asemeja a la de un lenguaje de programación. No es un lenguaje real y no se puede compilar, pero es perfecto para planificar la lógica. Herramientas como PSeInt son geniales para practicar esto.

Definición: Diagrama de Flujo (Ordinograma)

Es una representación gráfica de un algoritmo. Usa símbolos estandarizados para representar los diferentes pasos (inicio/fin, proceso, entrada/salida, decisión) y flechas para indicar el flujo de ejecución.

Ejemplo: Algoritmo para saber si un número es par o impar

Pseudocódigo (estilo PSeInt):

Algoritmo ParOImpar
    Definir numero Como Entero
    Escribir "Introduce un número:"
    Leer numero
    Si numero % 2 == 0 Entonces
        Escribir "El número es PAR"
    Sino
        Escribir "El número es IMPAR"
    FinSi
FinAlgoritmo

Diagrama de Flujo:

flowchart TD
    A[Inicio] --> B{Leer número};
    B --> C{"numero % 2 == 0?"};
    C -- "Sí" --> D["Escribir 'Es PAR'"];
    C -- "No" --> E["Escribir 'Es IMPAR'"];
    D --> F[Fin];
    E --> F;

    style A fill:#2ecc71,stroke:#333,stroke-width:2px
    style F fill:#e74c3c,stroke:#333,stroke-width:2px
    style C fill:#3498db,stroke:#333,stroke-width:2px,color:#fff

Reflexión

  1. ¿Qué ventajas tiene el pseudocódigo sobre el diagrama de flujo? ¿Y al revés?
  2. Intenta escribir en pseudocódigo el algoritmo para calcular la media de dos números.
  3. Dibuja el diagrama de flujo para el mismo algoritmo de la media de dos números.



3. Programación Estructurada

A finales de los 60, el software era un caos. Los programadores usaban una instrucción llamada GOTO que permitía saltar de cualquier punto del programa a cualquier otro. El resultado era el famoso "código espagueti": un lío de saltos imposible de seguir, depurar o mantener.

El GOTO es el Lado Oscuro

Edsger Dijkstra, uno de los padres de la informática, escribió en 1968 un famoso artículo llamado "Go To Statement Considered Harmful" (La sentencia GOTO considerada dañina). Demostró que su uso indiscriminado era una de las principales fuentes de errores en programación.

La programación estructurada nació como la solución a este caos. El Teorema del Programa Estructurado (de Böhm y Jacopini) demostró matemáticamente que cualquier algoritmo, por complejo que sea, puede construirse usando únicamente tres estructuras de control básicas:

  1. Secuencia: Ejecutar instrucciones una detrás de otra.
  2. Selección (o Condicional): Elegir entre dos o más caminos de ejecución (if, switch).
  3. Iteración (o Bucle): Repetir un bloque de código (while, for).

Estos tres pilares son la base de casi todos los lenguajes de programación modernos, incluido JAVA. Dominarlos es dominar la lógica fundamental de la programación.



4. Secuencias

Es la estructura más simple y natural. El código se ejecuta en el orden en que está escrito, de arriba hacia abajo, como leer un libro.

graph TD
    A(Inicio) --> B[Paso 1: Leer datos del usuario];
    B --> C[Paso 2: Realizar un cálculo];
    C --> D[Paso 3: Mostrar resultado];
    D --> E(Fin);

    style A fill:#f9f,stroke:#333,stroke-width:2px
    style E fill:#f9f,stroke:#333,stroke-width:2px

4.1 Ejemplos de Ejecución Secuencial

El siguiente programa JAVA es un ejemplo puro de secuencia. Cada línea se ejecuta después de la anterior, sin saltos ni repeticiones.

import java.util.Scanner;

public class SaludoSecuencial {
    public static void main(String[] args) {
        // 1. Inicia el programa
        Scanner scanner = new Scanner(System.in);

        // 2. Se ejecuta esta instrucción de salida
        System.out.println("¡Bienvenido! Vamos a crear tu perfil de jugador.");

        // 3. Se ejecuta esta instrucción
        System.out.print("Por favor, introduce tu nombre de usuario: ");

        // 4. El programa se detiene aquí y espera una entrada. Es secuencial pero bloqueante.
        String nombreUsuario = scanner.nextLine();

        // 5. Una vez que el usuario pulsa Enter, la ejecución continúa aquí.
        System.out.println("¡Hola, " + nombreUsuario + "! Un gran nombre para un campeón.");

        // 6. Finalmente, se ejecuta esta última instrucción y el programa termina.
        System.out.println("Perfil creado con éxito.");

        scanner.close(); // Buena práctica: cerrar el scanner cuando no se usa más.
    }
}

4.2 Secuencias Avanzadas: Streams en JAVA

Aunque el flujo básico es secuencial, JAVA moderno (desde la versión 8) introduce el concepto de Streams, que permite procesar secuencias de datos de una manera más declarativa y funcional. Aunque parece que todo ocurre a la vez, internamente sigue un flujo de procesamiento secuencial (a menos que se especifique lo contrario).

import java.util.List;
import java.util.stream.Collectors;

public class SecuenciaConStreams {
    public static void main(String[] args) {
        List<String> videojuegos = List.of("Cyberpunk 2077", "Baldur's Gate 3", "Starfield", "Elden Ring");

        // Esta es una cadena de operaciones secuenciales sobre la lista
        List<String> juegosConMasDe10LetrasEnMayusculas = videojuegos.stream() // 1. Convierte la lista en una secuencia (stream)
            .filter(juego -> juego.length() > 10)     // 2. Filtra los juegos con nombres de más de 10 letras
            .map(String::toUpperCase)                 // 3. Convierte los nombres restantes a mayúsculas
            .collect(Collectors.toList());            // 4. Recolecta los resultados en una nueva lista

        System.out.println(juegosConMasDe10LetrasEnMayusculas);
        // Salida: [CYBERPUNK 2077, BALDUR'S GATE 3, ELDEN RING]
    }
}

Reflexión

  1. ¿Un programa que solo contenga declaraciones de variables y operaciones de asignación es un ejemplo de secuencia? ¿Por qué?
  2. En el ejemplo SaludoSecuencial, ¿qué pasaría si la línea String nombreUsuario = scanner.nextLine(); se colocara antes de la línea System.out.print(...)? ¿El programa seguiría funcionando? ¿Cómo cambiaría la experiencia del usuario?



5. Condicionales

La vida está llena de decisiones, y los programas también. Los condicionales permiten que nuestro código elija un camino u otro basándose en si una condición es true o false. Es el "depende" de la programación.

¡Excelente idea! Es fundamental diferenciar bien la estructura if simple de la if-else. Aquí tienes el punto desarrollado para el condicional sencillo, siguiendo el mismo estilo y formato del ejemplo que me proporcionaste.

5.1. Condicional Básico (if único)

Esta es la forma más simple de decisión. Se utiliza cuando queremos ejecutar un bloque de código solo si una condición específica se cumple. A diferencia del if-else, aquí no hay un camino alternativo. Si la condición es falsa, el programa simplemente ignora el bloque if y continúa con la siguiente instrucción.

graph TD
    A1(Inicio) --> B1{¿Condición?};
    B1 -- Sí --> C1[Ejecutar Acción];
    C1 --> D1(Fin);
    B1 -- No --> D1;

Es como un bonus en un videojuego: solo si cumples el requisito (ej. encontrar un tesoro), obtienes la recompensa. Si no, el juego simplemente sigue su curso normal.

Ejemplo: Mensaje especial por Puntuación Alta

import java.util.Scanner;

public class PuntuacionFinal {
    public static void main(String[] args) {
        // Definimos una constante para el umbral del récord. ¡Buenas prácticas!
        final int PUNTUACION_PARA_RECORD = 10000;

        Scanner teclado = new Scanner(System.in);

        System.out.print("Introduce tu puntuación final: ");
        int puntuacion = teclado.nextInt();

        // La condición a evaluar: ¿supera la puntuación el récord?
        if (puntuacion > PUNTUACION_PARA_RECORD) {
            // Este bloque SÓLO se ejecuta si la condición es true.
            System.out.println("¡NUEVO RÉCORD! ¡Has entrado en el salón de la fama!");
        }

        // Esta línea se ejecuta SIEMPRE, sin importar el resultado del 'if'.
        System.out.println("Partida terminada. Tu puntuación ha sido registrada.");

        teclado.close();
    }
}

5.2. Condicional Simple (if-else)

Es la bifurcación más básica: si se cumple una condición, haz una cosa; si no, haz otra.

graph TD
    A2(Inicio) --> B2{¿Condición?};
    B2 -- Sí --> C2[Acción si es Verdadero];
    B2 -- No --> D2[Acción si es Falso];
    C2 --> E2(Fin);
    D2 --> E2;

Ejemplo: Login de un videojuego

import java.util.Scanner;

public class LoginSimple {
    public static void main(String[] args) {
        final String USUARIO_CORRECTO = "GeraltDeRivia";
        final String PASSWORD_CORRECTA = "RoachIsTheBest";

        Scanner teclado = new Scanner(System.in);

        System.out.print("Usuario: ");
        String usuario = teclado.nextLine();
        System.out.print("Contraseña: ");
        String password = teclado.nextLine();

        // La condición a evaluar: ¿coinciden usuario Y contraseña?
        if (usuario.equals(USUARIO_CORRECTO) && password.equals(PASSWORD_CORRECTA)) {
            // Rama VERDADERA (si la condición es true)
            System.out.println("¡Bienvenido, Brujo! Se han cargado tus pociones.");
        } else {
            // Rama FALSA (si la condición es false)
            System.out.println("Credenciales incorrectas. Un drowner te ha comido.");
        }
        teclado.close();
    }
}

¡No uses == para comparar Strings!

En JAVA, el operador == compara si dos referencias de objetos apuntan a la misma dirección de memoria, no si su contenido es el mismo. Para comparar el contenido de dos Strings, siempre debes usar el método .equals().

5.3. Condicionales Múltiples (if-else if-else)

A veces, hay más de dos caminos posibles. Para eso, encadenamos condiciones con else if.

graph TD
    A3(Inicio) --> B3[¿Condición 1?];
    B3 -- Sí --> C3[Acción 1];
    B3 -- No --> D3[¿Condición 2?];
    D3 -- Sí --> E3[Acción 2];
    D3 -- No --> F3[Acción por defecto del else];
    C3 --> G3(Fin);
    E3 --> G3;
    F3 --> G3;

Ejemplo: Sistema de "loot" (botín) de un enemigo

import java.util.Random;

public class SistemaDeLoot {
    public static void main(String[] args) {
        // Generamos un número aleatorio entre 1 y 100 para simular la suerte
        int tiradaDeSuerte = new Random().nextInt(100) + 1;

        System.out.println("Has derrotado a un dragón. Tu tirada de suerte es: " + tiradaDeSuerte);

        if (tiradaDeSuerte > 95) { // Un 5% de probabilidad (96, 97, 98, 99, 100)
            System.out.println("¡LOOT LEGENDARIO! Has conseguido la Espada de Fuego del Dragón.");
        } else if (tiradaDeSuerte > 75) { // Un 20% de probabilidad
            System.out.println("¡Loot Épico! Has conseguido una pechera de escamas de dragón.");
        } else if (tiradaDeSuerte > 40) { // Un 35% de probabilidad
            System.out.println("Loot Raro. Has conseguido 500 monedas de oro.");
        } else { // El 40% restante
            System.out.println("Loot Común. Has conseguido una garra de dragón rota.");
        }
    }
}

5.4. Estructura switch

Cuando tienes que comprobar una misma variable contra múltiples valores discretos (números, caracteres, Strings), usar una cadena larga de if-else if puede volverse feo y repetitivo (el "Efecto Hadouken"). La estructura switch es una alternativa más limpia y, a menudo, más eficiente.

graph TD
    A4(Inicio) --> B4[Leer variable];
    B4 --> C4{{Evaluar variable}};
    C4 -- Caso 1 --> D4[Acción para Caso 1];
    C4 -- Caso 2 --> E4[Acción para Caso 2];
    C4 -- Otro --> F4[Acción por defecto];
    D4 --> G4(Fin);
    E4 --> G4;
    F4 --> G4;

Ejemplo: Menú de opciones

import java.util.Scanner;

public class MenuConSwitch {
    public static void main(String[] args) {
        System.out.println("== MENÚ PRINCIPAL ==");
        System.out.println("1. Nueva Partida");
        System.out.println("2. Cargar Partida");
        System.out.println("3. Opciones");
        System.out.println("4. Salir");
        System.out.print("Elige una opción: ");

        Scanner scanner = new Scanner(System.in);
        int opcion = scanner.nextInt();

        // Switch tradicional (hasta JAVA 11)
        switch (opcion) {
            case 1:
                System.out.println("Iniciando una nueva aventura...");
                break; // ¡El break es crucial para no seguir al siguiente caso!
            case 2:
                System.out.println("Cargando tu último punto de guardado...");
                break;
            case 3:
                System.out.println("Mostrando menú de opciones gráficas y de sonido...");
                break;
            case 4:
                System.out.println("¡Gracias por jugar!");
                break;
            default: // Se ejecuta si no coincide ningún caso
                System.out.println("Opción no válida. Por favor, elige un número del 1 al 4.");
                break;
        }

        // Switch Expression (JAVA 12+), más moderno y seguro
        String mensaje = switch (opcion) {
            case 1 -> "Iniciando una nueva aventura...";
            case 2 -> "Cargando tu último punto de guardado...";
            case 3 -> "Mostrando menú de opciones gráficas y de sonido...";
            case 4 -> "¡Gracias por jugar!";
            default -> "Opción no válida. Por favor, elige un número del 1 al 4.";
        }; // El punto y coma va al final de la expresión
        System.out.println("Mensaje del Switch Expression: " + mensaje);
        scanner.close();
    }
}

Switch Expressions: el futuro es hoy

Desde JAVA 12, las "Switch Expressions" (con la sintaxis de flecha ->) son la forma recomendada. Son más seguras porque te obligan a cubrir todos los casos posibles (o usar default), eliminan la necesidad del break (evitando errores de "caída"), y pueden devolver un valor, lo que las hace increíblemente potentes.

Reflexión

  1. ¿Podrías reescribir el ejemplo del sistema de "loot" usando una estructura switch? ¿Sería una buena idea? ¿Por qué sí o por qué no?
  2. En el switch tradicional, ¿qué pasaría si olvidamos poner un break en el case 1?
  3. ¿En qué situaciones un if-else if es preferible a un switch? (Pista: piensa en las condiciones que puedes evaluar).



6. Bucles

Los bucles son el corazón de la automatización. Nos permiten ejecutar un bloque de código una y otra vez sin tener que copiarlo y pegarlo. Hay dos grandes familias: los bucles indefinidos (que se ejecutan mientras una condición sea cierta) y los definidos (que se ejecutan un número específico de veces).

6.1. Bucles Indefinidos

while

El bucle while es el más fundamental. Es como un guardia de seguridad en una puerta: primero comprueba la condición (mira tu pase), y si es true, te deja pasar (ejecuta el bloque). Repite este proceso hasta que la condición sea false.

flowchart TD
    A([Inicio]) --> B{Condición}
    B -- SI --> C[Acciones dentro del bucle]
    C --> B
    B -- No --> D([FIN])

Bucle while

Ejemplo: Simulación de descarga de un archivo

public class DescargaWhile {
    public static void main(String[] args) {
        int porcentajeDescargado = 0;
        int velocidadDescarga = 10; // en % por segundo

        // Mientras el porcentaje sea menor que 100...
        while (porcentajeDescargado < 100) {
            System.out.println("Descargando... " + porcentajeDescargado + "%");
            porcentajeDescargado += velocidadDescarga; // Avanzamos la descarga

            // Simulamos una pequeña pausa
            try {
                Thread.sleep(500); // Pausa de medio segundo
            } catch (InterruptedException e) {
                // Manejo de la excepción, aunque aquí no haremos nada
            }
        }
        System.out.println("¡Descarga completada! (100%)");
    }
}

do-while

El do-while es el primo "actúa primero, pregunta después" del while. Siempre ejecuta el bloque de código al menos una vez, y luego comprueba la condición al final para decidir si repite.

flowchart TD
    A([Inicio]) --> B[Acciones dentro del bucle]
    B --> C{Condición}
    C -- SI --> B
    C -- NO --> D([FIN])

Bucle do-while

Ejemplo: Menú de usuario que se repite hasta que elige salir

import java.util.Scanner;

public class MenuDoWhile {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int opcion;

        do {
            System.out.println("\n--- Menú ---");
            System.out.println("1. Jugar");
            System.out.println("2. Ver Puntuaciones");
            System.out.println("0. Salir");
            System.out.print("Elige una opción: ");
            opcion = scanner.nextInt();

            // Aquí iría un switch para manejar la opción
            // ...

        } while (opcion != 0); // La condición se comprueba al final

        System.out.println("¡Hasta la próxima!");
        scanner.close();
    }
}

While vs. Do-While

  • Usa while cuando es posible que el bucle no se ejecute ni una sola vez.
  • Usa do-while cuando necesitas garantizar que el bloque se ejecute al menos una vez, típicamente en menús o para validar entradas de usuario.

Comparar bucles while

6.2. Bucles Definidos (for)

El bucle for es el especialista en iterar un número conocido de veces. Es perfecto para recorrer arrays, contar hasta un número o, en general, cuando sabes de antemano cuántas repeticiones necesitas.

flowchart TD
    A([Inicio]) --> B[Iniciar i = 0]
    B --> C{¿i < N?}
    C -- SI --> D[Acciones dentro del bucle]
    D --> E[i++]
    E --> C
    C -- NO --> F([FIN])

Bucle while

Bucle for clásico

Consta de tres partes separadas por punto y coma: 1. Inicialización: Se ejecuta una sola vez al principio. (Ej: int i = 0) 2. Condición: Se comprueba antes de cada iteración. (Ej: i < 10) 3. Actualización: Se ejecuta al final de cada iteración. (Ej: i++)

Ejemplo: Lanzar 5 dados

import java.util.Random;

public class LanzarDados {
    public static void main(String[] args) {
        Random random = new Random();

        // Repetimos el bloque 5 veces (para i = 0, 1, 2, 3, 4)
        for (int i = 1; i <= 5; i++) {
            int resultadoDado = random.nextInt(6) + 1; // Un número entre 1 y 6
            System.out.println("Lanzamiento " + i + ": " + resultadoDado);
        }
    }
}

Para ilustrar mejor el concepto, veamos cómo repetir un bloque de instrucciones múltiples veces. Por ejemplo:

Supongamos que la variable i comienza con los valores 0 y 1. El objetivo es repetir un bloque de código mientras se cumpla una condición específica, incrementando el valor de i en 1 en cada iteración del bucle. A continuación, se presenta un ejemplo de un bucle for junto con su salida correspondiente:

Ejemplos For

Podemos variar en la forma que se incrementa el contador i, teniendo:

For cambiando índice

Es importante realizar una traza para entender como funciona un bucle for:

Traza bucle for

Bucle for-each (o mejorado)

Es una versión simplificada y más segura del for, diseñada específicamente para recorrer todos los elementos de una colección (como un array o una lista).

Ejemplo: Mostrar el inventario de un personaje

public class Inventario {
    public static void main(String[] args) {
        String[] inventario = {"Espada de Acero", "Poción de Vida", "Escudo de Roble", "Mapa del Tesoro"};

        System.out.println("Inventario del Héroe:");
        // Por cada "item" de tipo String en el array "inventario"...
        for (String item : inventario) {
            // ...ejecuta este bloque
            System.out.println("- " + item);
        }
    }
}

Reflexión

  1. ¿Usarías un while o un for para leer un fichero de texto hasta que se acabe? ¿Por qué?
  2. ¿Podrías reescribir el ejemplo de "Lanzar Dados" usando un bucle while? ¿Cuál de las dos versiones te parece más clara?
  3. En el bucle for-each, ¿tienes acceso al índice (la posición) del elemento en el array? ¿Cómo lo harías si necesitaras saber la posición de cada item?


Sentencia Break

El comportamiento de los bucles puede alterarse utilizando las instrucciones break y continue. La sentencia break interrumpe inmediatamente el bucle, omitiendo las instrucciones restantes y saliendo del mismo.

System.out.println("Iniciando...");
for (int contador = 1; contador <= 15; contador++) {
    System.out.println("Iteración: " + contador);
    if (contador == 12) {
        break;
    }
    System.out.println("Finalizada iteración: " + contador);
}
System.out.println("Finalizado");

En este ejemplo, el mensaje "Finalizada iteración" no se mostrará para la iteración donde contador es igual a 12, ni se ejecutarán las iteraciones para contador igual a 13, 14 o 15, ya que el bucle se interrumpe al alcanzar la condición del break.

Sentencia Continue

La sentencia continue permite omitir las instrucciones restantes dentro de un bucle, pero no termina el bucle, sino que avanza directamente a la siguiente iteración (o "ciclo").

System.out.println("Iniciando...");
for (int contador = 1; contador <= 5; contador++) {
    System.out.println("Ciclo: " + contador);
    if (contador == 3)
        continue;
    System.out.println("Finalizado ciclo: " + contador);
}
System.out.println("Finalizado");

En este ejemplo, no se mostrará el texto "Finalizado ciclo" para el ciclo cuando contador es igual a 3, pero el bucle continuará con las iteraciones para contador igual a 4 y 5.

Bucles for anidados

Los bucles for se pueden anidar para dar solución a problemas complejos el trabajo con matrices. Consiste en poner un bucle dentro de otro.

Traza bucle for

Ejemplo de como imprimir una forma rectangular con "#":

Ejemplo anidado



Aplicación en el Mundo Real

Las estructuras que hemos visto en este tema no son conceptos teóricos; son el día a día de cualquier desarrollador. Cada aplicación, web o videojuego que usas está construida sobre estos cimientos de secuencias, decisiones y repeticiones.

  • Validación de Formularios: Cuando te registras en una web, un if comprueba si la contraseña tiene más de 8 caracteres, otro if verifica si los dos campos de email coinciden, y un for podría recorrer la lista de nombres de usuario ya existentes para asegurarse de que el tuyo es único.
  • Videojuegos: La lógica de un videojuego es un festival de condicionales y bucles.
    • Un bucle while (juegoEnMarcha) es el corazón del juego, repitiendo constantemente las acciones de actualizar la pantalla, leer los controles del jugador y mover a los personajes.
    • if (jugador.vida <= 0) decide si se muestra la pantalla de "Game Over".
    • Un bucle for-each recorre la lista de todos los enemigos en pantalla para actualizar su posición y su inteligencia artificial en cada fotograma.
  • Procesamiento de Datos: Imagina que Netflix quiere recomendarte series. Un programa podría usar un bucle for para recorrer todo tu historial de visualización. Dentro del bucle, usaría condicionales if-else if para puntuar cada serie según su género, actores y tu calificación. Finalmente, otro bucle for recorrería la lista de puntuaciones para mostrarte las 5 más altas.
  • Cajeros Automáticos (ATM): Cuando usas un cajero, un bucle do-while mantiene el menú principal en pantalla hasta que seleccionas la opción de "Salir". Un switch gestiona la opción que eliges ("Consultar saldo", "Retirar dinero", etc.), y un if comprueba si tienes saldo suficiente antes de permitirte retirar dinero.

Dominar el flujo de control con if, switch, for y while es, literalmente, aprender a dar vida y lógica a un programa. Son las herramientas con las que le enseñas a la máquina a reaccionar, a adaptarse y a realizar tareas complejas de forma automática.



Para Saber Más

Practicar la lógica de la programación estructurada es la clave para convertirse en un buen desarrollador. La sintaxis de JAVA se aprende, pero la habilidad para resolver problemas es lo que marca la diferencia.

  1. CodingBat (Java Section): Una de las mejores plataformas para empezar. Ofrece cientos de problemas pequeños y autocontenidos sobre lógica, bucles, Strings y arrays. Escribes el código directamente en el navegador y te lo corrige al instante. Es un gimnasio para el cerebro del programador.
  2. HackerRank (Problem Solving): Sube un nivel de dificultad respecto a CodingBat. Los problemas aquí son más parecidos a los que te encontrarías en una entrevista de trabajo. Empieza por los de dificultad "Easy" para afianzar los conceptos de bucles y condicionales.
  3. Tutoriales Oficiales de Java sobre Estructuras de Control (Oracle): La fuente oficial. Es una documentación más densa, pero es el lugar perfecto para consultar detalles específicos sobre el funcionamiento de cada estructura (if-else, switch, for, etc.) según el estándar de JAVA. (En inglés).
  4. Proyecto Euler: Una colección de problemas matemáticos y de programación que requieren ingenio y un uso eficiente de los bucles y condicionales. Es un reto excelente para cuando ya te sientas cómodo con lo básico y quieras poner a prueba tu capacidad para optimizar algoritmos.




EJERCICIOS CONDICIONES


=> Ejercicios de Java - Estructuras condicionales

EJERCICIOS BUCLES


=> Ejercicios de Java - Bucles





---

Depuración en Java

Deparación con IntelliJ