====== 02.1 - Fundamentos de programación en Java ====== ===== 1. Lenguajes de programación ===== Podemos clasificar los lenguajes de programación atendiendo a distintos criterios ==== 1.1 Según su nivel de abstracción ==== * **Lenguajes de bajo nivel:** Están muy cercanos al hardware. Dependen de la arquitectura de la máquina, por lo que **no son portables** (no se pueden ejecutar en otra máquina distinta a aquella para la que se programaron). A cambio, aprovechan al máximo el rendimiento del hardware. * **Lenguaje máquina:** Único lenguaje que comprende directamente el procesador. Formado exclusivamente por cadenas de bits ($0$ y $1$). * **Lenguaje ensamblador:** Representación simbólica (mnemónicos) del código máquina. Cada arquitectura de procesador tiene su propio ensamblador. Ejemplo de código máquina: 0010 0000 1001 1001 1000 1110 Ejemplo de lenguaje ensamblador: MOV R1, DPL CJNE R1, #78H, T05SEG RET * **Lenguajes de alto nivel:** Se aproximan al lenguaje natural humano e ignoran las complejidades del hardware. Son **portables** entre distintas arquitecturas. Ejemplos: Java, Python, C#, JavaScript, PHP. ==== 1.2 Según cómo gestionan los tipos de sus datos ==== * **Estaticidad:** * **Tipado estático:** Exige declarar el tipo de dato de cada variable. Este tipo se verifica en tiempo de compilación y no puede cambiar en ejecución (salvo conversiones explícitas o **casting**). Ejemplos: Java, C++, C#. * **Tipado dinámico:** No requiere indicar el tipo de variable al declararla; el lenguaje lo determina en tiempo de ejecución y la variable puede cambiar de tipo libremente. Ejemplos: Python, JavaScript. * **Fortaleza:** * **Tipado fuerte:** No permite realizar operaciones entre tipos incompatibles ni conversiones implícitas no seguras. * **Tipado débil:** Realiza conversiones de tipo automáticas ante ciertas operaciones. ==== 1.3 Según su forma de ejecución ==== * **Lenguajes compilados:** Necesitan un programa **compilador** que traduce todo el código fuente a lenguaje máquina antes de su ejecución (ej. C, C++). * **Lenguajes interpretados:** Un programa **intérprete** lee y traduce el código fuente a lenguaje máquina línea a línea, en tiempo real mientras se ejecuta (ej. Python, JavaScript). ===== 2. Elementos de un lenguaje de programación ===== Hay una serie de elementos que están presentes en casi todo lenguaje de programación: * **Tipos de datos**: enteros, reales, cadena de caracteres... * **Palabras reservadas**: conjunto de palabras propias del lenguaje de programación. No pueden utilizarse, por ejemplo, para nombrar variables o constantes. * **Operadores**: Crean instrucciones utilizando cálculos matemáticos (operadores aritméticos), comparaciones (operadores relacionales) u operaciones lógicas (operadores lógicos). * **Constantes y variables**: Posiciones de memoria que almacenan los datos con los que trabaja nuestro programa. Las constantes no pueden variar a lo largo de la ejecución del programa, mientras que las variables sí. * **Estructuras de control**: Secuenciales, condicionales o bucles. * **Funciones**: Bloques de código empaquetados con una cabecera que realizan una tarea específica, lo que permite, entre otras cosas, la reutilización del código. * **Comentarios**: Bloques dentro del código que no se ejecutan. Su función es la de aclarar partes del programa. ===== 3. Introducción al lenguaje Java ===== Java es un lenguaje de programación de alto nivel, de propósito general y con una sintaxis ampliamente derivada de C/C++. Fue creado originalmente por **Sun Microsystems** (actualmente propiedad de Oracle) en 1995. Entre sus pilares fundamentales destacan: * **Lenguaje híbrido (compilado e interpretado):** Combina ambas técnicas mediante un proceso en dos fases: - **Compilación:** Traduce el código fuente (archivos ''.java'') a un código intermedio universal llamado **bytecode** (archivos ''.class''). - **Ejecución e interpretación:** La **Máquina Virtual de Java** (**JVM**) interpreta ese //bytecode// en la máquina de destino. Gracias a este modelo, el programa es totalmente **portable**: se puede ejecutar en cualquier sistema operativo o arquitectura que disponga de la //JVM// (*"Escríbelo una vez, ejecútalo en cualquier lugar"*). * **Orientación a Objetos (POO):** Salvo los tipos primitivos, todo el código se estructura en clases y objetos, lo que favorece la modularidad, el mantenimiento y la reutilización. * **Gran ecosistema y librerías:** Incluye una amplia biblioteca estándar (API) que proporciona herramientas listas para usar en casi cualquier tipo de desarrollo (gestión de archivos, estructuras de datos, redes, etc.). **Java es un lenguaje de tipado estático y fuertemente tipado** (aunque realiza pequeñas conversiones implícitas seguras, como concatenar números a cadenas de texto). public class Tipado { static void main() { int a = 5; // a = "Hola"; // ERROR de compilación: No se puede asignar un String a una variable int. String b = "Texto: "; IO.println(b + a); // Concatenación implícita: convierte 'a' a String ("Texto: 5"). } } ==== 3.1 Compilación y ejecución de aplicaciones Java ==== Los archivos de código fuente en Java tienen la extensión ''.java'' y su nombre **debe coincidir exactamente con la clase principal** que contienen. public class HolaMundo { static void main() { IO.println("Hola Mundo"); } } Existen dos formas de ejecutar este programa desde la consola: **1. Ejecución directa en un solo paso**: Ideal para pruebas rápidas. Compila en memoria y ejecuta el resultado de forma transparente: java HolaMundo.java **2. Proceso tradicional en dos pasos (entornos de producción):** 1. Compilar el código fuente a bytecode (genera el archivo ''HolaMundo.class''): javac HolaMundo.java 2. Ejecutar el bytecode compilado en la JVM: java HolaMundo Al ejecutar con el comando ''java'' un archivo ya compilado, **no debemos poner la extensión ''.class''**, solo el nombre de la clase. Esta ejecución es más rápida porque no requiere recompilar el código. ==== 3.2 Generación de archivos .class ==== El compilador de Java genera **un archivo ''.class'' por cada clase definida** en nuestro código fuente, independientemente de si están en el mismo archivo ''.java'' o en archivos separados. public class HolaMundo2 { static void main() { IO.println("Hola Mundo 2"); } } class HolaMundo3 { static void main() { IO.println("Hola Mundo 3"); } } Al ejecutar ''javac HolaMundo2.java'', se generarán dos archivos independientes: ''HolaMundo2.class'' y ''HolaMundo3.class''. **Regla de visibilidad de archivos:** Un archivo ''.java'' solo puede contener **una clase pública (''public'')** como máximo, y el nombre del archivo debe coincidir exactamente con el nombre de dicha clase. ==== 3.3 Estructura de una aplicación Java ==== Vamos a ver la estructura del ejemplo anterior //HolaMundo.java//: public class HolaMundo { static void main() { IO.println("Hola Mundo"); } } * **''public class HolaMundo''**: Define la clase. Todo código en Java debe residir dentro de una clase. * **Llaves (''{'' ''}'')**: Delimitan los bloques de código (clases, métodos, bucles, etc.). * **''static void main()''**: Es el **método de entrada** (punto de inicio) de la aplicación. La JVM busca y ejecuta automáticamente este método al arrancar el programa. * **''IO.println(...)''**: Instrucción que imprime el texto indicado por pantalla y añade un salto de línea. ==== 3.4 Secuencia de instrucciones ==== Las instrucciones se ejecutan secuencialmente (de arriba a abajo) y **deben terminar obligatoriamente con punto y coma ('';'')**. static void main() { IO.println("Primera instrucción"); IO.println("Segunda instrucción"); } ==== 3.5 Indentación del código ==== Aunque Java no obliga a sangrar el código para que compile (a diferencia de Python), la **indentación es fundamental** para garantizar la legibilidad del proyecto. Se recomienda usar **4 espacios** por cada nivel de anidamiento. Ejemplo con correcta indentación: public class HolaMundo { static void main() { IO.println("Código fácil de leer"); } } ==== 3.6 Comentarios ==== Los comentarios son anotaciones dentro del código fuente que el compilador ignora por completo. Su objetivo es explicar la lógica del programa, añadir notas aclaratorias o generar documentación técnica. En Java podemos escribir comentarios de tres formas: * **Comentarios de una sola línea:** Empiezan con dos barras (''//''). * **Comentarios de bloque (multilínea):** Se encierran entre ''/*'' y ''*/''. * **Comentarios de documentación (JavaDoc):** Se encierran entre ''/**'' y ''*/'' para generar documentación técnica en HTML. /** * Comentario de documentación (JavaDoc). * Muestra información sobre la clase o el autor. */ public class HolaMundo { static void main() { /* * Comentario de bloque o multilínea. * Útil para detallar algoritmos o bloques * de código complejos. */ // Comentario de una línea: explica la instrucción siguiente IO.println("Hola mundo"); } } **No abuses de los comentarios:** Como indica Robert C. Martin en su libro //Código limpio// (Clean Code), el mejor comentario es el que no hace falta escribir. Es preferible escribir código expresivo y autoexplicativo (utilizando buenos nombres de variables y métodos) antes que llenar el programa de comentarios redundantes. **Limpia el código "muerto":** Durante la fase de desarrollo es habitual comentar bloques de código para realizar pruebas. Es importante eliminar todo ese código comentado antes de finalizar y entregar el proyecto, ya que genera ruido y confunde a otros programadores. ===== 4. Elementos básicos en Java ===== ==== 4.1 Variables ==== Una variable es una posición de memoria con un nombre simbólico asignado donde se almacena un valor susceptible de cambiar a lo largo del programa. Una constante, por el contrario, mantiene un valor fijo e inmutable durante toda la ejecución. === Declaración e inicialización === Para utilizar una variable en Java debemos especificar su **tipo de dato** seguido de su **nombre** (identificador). Podemos reservar el espacio en memoria (declarar) y asignarle un valor (inicializar) en pasos separados o en una sola línea: static void main() { // 1. Declaración (reserva espacio en memoria, sin valor inicial) int edad; // 2. Inicialización (asignación del primer valor) edad = 25; // 3. Declaración e inicialización simultánea (lo más habitual) double precio = 19.99; boolean activo = true; } En Java, las **variables locales deben inicializarse antes de ser utilizadas**. Intentar leer o imprimir el valor de una variable que no ha sido inicializada provocará un error de compilación. === Tipos primitivos === Java maneja 8 tipos de datos primitivos que representan valores simples (enteros, decimales, caracteres o booleanos). ^ Tipo ^ Tamaño (bits) ^ Descripción ^ Valor por defecto ^ | ''byte'' | 8 | Entero muy pequeño con signo $[-128, 127]$ | $0$ | | ''short'' | 16 | Entero corto con signo $[-32768, 32767]$ | $0$ | | ''int'' | 32 | Entero estándar $[-2^{31}, 2^{31}-1]$ | $0$ | | ''long'' | 64 | Entero largo $[-2^{63}, 2^{63}-1]$ | $0L$ | | ''float'' | 32 | Decimal en coma flotante de precisión simple | $0.0f$ | | ''double'' | 64 | Decimal en coma flotante de precisión doble | $0.0d$ | | ''char'' | 16 | Carácter individual en formato Unicode | $'\u0000'$ | | ''boolean'' | 1 | Valor lógico (''true'' o ''false'') | ''false'' | === Inferencia de tipos (var) === Desde Java 10, es posible omitir la declaración explícita del tipo en variables locales utilizando la palabra reservada ''var''. El compilador deducirá el tipo basándose en el valor asignado: var edad = 25; // Compila como int var sueldo = 1250.50; // Compila como double var texto = "Hola"; // Compila como String Por defecto, Java interpreta los literales enteros como ''int'' y los decimales como ''double''. Si necesitas asignar un valor de tipo ''float'' o ''long'', debes añadir un sufijo al literal: float pi = 3.1416f; // Requiere 'f' para no ser tratado como double long distancia = 3000000000L; // Requiere 'L' === Conversión de tipos (Cast) === Cuando necesitamos convertir el valor de un tipo primitivo a otro, utilizamos la conversión explícita mediante el operador de **cast**: ''(tipo) expresion''. public class Cast { static void main() { // Cast directo (truncamiento) double precioDouble = 19.99; int precioEntero = (int) precioDouble; // Pierde los decimales -> 19 // Evitar división entera no deseada int a = 9; int b = 2; float resultadoIncorrecto = (float) (a / b); // Divide como int (4) y lo convierte a float -> 4.0f float resultadoCorrecto = (float) a / b; // Convierte 'a' a float (9.0f) y divide -> 4.5f } } El operador ''(tipo)'' solo funciona entre tipos primitivos compatibles. Para convertir de ''String'' a un tipo numérico, debemos usar los métodos ''parse'' de sus respectivas clases envoltorio (**wrapper classes**): String textoNumero = "42"; int numero = Integer.parseInt(textoNumero); double decimal = Double.parseDouble("3.14"); === Nombres y reglas de estilo === == Reglas de sintaxis == * Un identificador puede contener letras, dígitos, guion bajo (''_'') y el símbolo de dólar (''$''). * Debe comenzar obligatoriamente por una letra, ''_'' o ''$''. Nunca por un número. * No se pueden utilizar **palabras reservadas** del lenguaje (''int'', ''class'', ''final'', ''public'', ''if'', etc.). == Buenas prácticas == * **Nombres autoexplicativos:** El nombre de la variable debe reflejar su propósito. Es preferible ''distanciaKilometros'' antes que ''d''. * **Estilo camelCase:** En Java, por convención, las variables se escriben en minúscula inicial y con las subsecuentes palabras en mayúscula (''alturaEdificio''). * **Inglés como estándar:** Se recomienda escribir el código en inglés para mantener un estándar internacional. ==== 4.2 Constantes ==== Las constantes almacenan información que no debe cambiar durante la ejecución. Se definen anteponiendo la palabra reservada ''final''. Por convención, sus nombres se escriben en **MAYÚSCULAS_CON_GUION_BAJO**. public class Constantes { static void main() { final double VELOCIDAD_LUZ = 299792.458; // en km/s final float PI = 3.1416f; // VELOCIDAD_LUZ = 300000.0; // ERROR de compilación: No se puede reasignar una variable final } } ==== 4.3 Operadores ==== === Operador de asignación y caracteres de escape === El operador principal de asignación es el signo igual (''=''). Según el tipo de dato asignado, debemos considerar ciertas reglas sintácticas: * Los valores de tipo ''char'' utilizan comillas simples ('''a'''). * Los valores de tipo ''String'' utilizan comillas dobles (''"Hola"''). * Para incluir caracteres especiales (como comillas dobles) dentro de un ''String'', se utiliza la barra invertida (''\'') como carácter de escape. static void main() { char letra = 'A'; boolean activo = true; String frase = "Dijo: \"Hola mundo\""; // Carácter de escape \" } === Operadores aritméticos y de asignación compuesta === Java ofrece operadores aritméticos básicos: suma (''+''), resta (''-''), multiplicación (''*''), división (''/'') y módulo o resto entero (''%''). Pueden combinarse con el operador de asignación para acortar instrucciones: ^ Operador ^ Ejemplo ^ Equivalencia ^ | ''+='' | ''a += b;'' | ''a = a + b;'' | | ''-='' | ''a -= b;'' | ''a = a - b;'' | | ''*='' | ''a *= b;'' | ''a = a * b;'' | | ''/='' | ''a /= b;'' | ''a = a / b;'' | | ''%='' | ''a %= b;'' | ''a = a % b;'' | === Operadores unarios === Operan sobre un único valor para modificar su signo, incrementarlo, decrementarlo o negarlo: * **Signo (''+'', ''-''):** Cambia o mantiene el signo del valor evaluado. * **Incremento y decremento (''++'', ''--''):** * **Post-incremento (''a++''):** Utiliza el valor actual de la variable en la expresión y **después** la incrementa en 1. * **Pre-incremento (''++a''):** Incrementa la variable en 1 **antes** de evaluar la expresión. * **Negación lógica (''!''):** Invierte un valor booleano (''!true'' es ''false''). public class OperadoresUnarios { static void main() { int a = 3; IO.println("Post-incremento (a++): " + a++); // Muestra 3, 'a' pasa a ser 4 IO.println("Valor tras a++: " + a); // Muestra 4 a = 3; IO.println("Pre-incremento (++a): " + ++a); // 'a' pasa a ser 4, Muestra 4 } } === Operadores relacionales === Comparan dos operandos y devuelven un valor booleano (''true'' o ''false''): ''=='' (igual a), ''!='' (distinto de), ''<'' (menor que), ''>'' (mayor que), ''<='' (menor o igual) y ''>='' (mayor o igual). Un error muy frecuente es confundir la asignación (''='') con la comparación de igualdad (''==''): int a = 5; int b = 2; // IO.println(a = b); // Asigna 2 a 'a' e imprime 2. ¡Sobrescribe la variable! IO.println(a == b); // Compara si 'a' es igual a 'b' e imprime false. === Operadores lógicos === Permiten combinar múltiples condiciones booleanas: * ''&&'' (AND): Devuelve ''true'' solo si ambas condiciones son verdaderas. * ''||'' (OR): Devuelve ''true'' si al menos una de las condiciones es verdadera. * ''!'' (NOT): Niega la condición evaluada. static void main() { int edad = 20; boolean tieneCarnet = true; boolean puedeConducir = edad >= 18 && tieneCarnet; // true } ===== 5. Estructuras de control ===== ==== 5.1 Entrada y Salida de datos con la clase IO ==== A partir de Java 25 utilizamos la clase ''IO'' para interactuar con la consola sin necesidad de realizar importaciones adicionales. === Salida por consola === * ''IO.println(...)'': Imprime el mensaje introducido y añade un salto de línea final. * ''IO.print(...)'': Imprime el mensaje manteniendo el cursor en la misma línea. === Entrada por teclado === El método ''IO.readln()'' pausa la ejecución hasta que el usuario escribe un texto y pulsa ''Intro''. Siempre retorna los datos en forma de cadena (''String''). Si se requiere un valor numérico, debemos realizar el parseo correspondiente: public class EntradaSalida { static void main() { String nombre = IO.readln("Introduce tu nombre: "); int edad = Integer.parseInt(IO.readln("Introduce tu edad: ")); IO.println("Hola " + nombre + ", tienes " + edad + " años."); } } ==== 5.2 Estructuras de selección ==== Permiten ejecutar un conjunto de instrucciones u otro en función del resultado de evaluar una condición lógica que devuelve un valor booleano (''true'' o ''false''). En Java existen tres estructuras de selección: ''if-else'', ''switch'' y el ''operador ternario''. === La sentencia if-else === La sentencia ''if'' evalúa una condición. Si esta resulta verdadera (''true''), se ejecuta el bloque de código delimitado entre llaves ''{}''. En caso contrario, el bloque se omite. public class SentenciaIf { static void main() { int number = 3; if (number < 5) { IO.println("El número es menor que 5"); } IO.println("Fin del programa"); } } **Atención a los límites de las condiciones**: Evaluar si un número es menor (''<'') no es lo mismo que evaluar si es menor o igual (''<=''). Para ''number = 5'', la condición ''number < 5'' devolverá ''false''. Un porcentaje elevado de los errores en programación principiante proviene de un mal ajuste en los operadores de comparación. == Condiciones compuestas == Podemos combinar varias expresiones lógicas en una sola condición utilizando los operadores lógicos ''&&'' (**AND**) y ''||'' (**OR**): if (number > 2 && number < 5) { IO.println("El número está estrictamente entre 2 y 5"); } == La cláusula else y else-if == Para ejecutar una alternativa cuando la condición resulte falsa, añadimos el bloque ''else''. Esto evita tener que evaluar condiciones mutuamente excluyentes dos veces de forma ineficiente. if (number < 5) { IO.println("El número es menor que 5"); } else { IO.println("El número es mayor o igual que 5"); } Si necesitamos evaluar múltiples alternativas en cadena, utilizamos ''else if'': if (number < 5) { IO.println("El número es menor que 5"); } else if (number < 8) { IO.println("El número está en el rango [5, 8)"); } else if (number < 10) { IO.println("El número está en el rango [8, 10)"); } else { IO.println("El número es mayor o igual que 10"); } Que un programa funcione no significa que sea eficiente. Encadenar múltiples bloques ''if'' independientes en lugar de usar ''else if'' obliga a la CPU a evaluar todas las condiciones, reduciendo el rendimiento en programas de gran escala. === La sentencia switch === Cuando debemos comparar una misma variable contra múltiples valores constantes, la sentencia ''switch'' ofrece una alternativa mucho más limpia, estructurada y legible que encadenar múltiples bloques ''else if''. En el Java moderno, ''switch'' no solo funciona como una instrucción de control de flujo, sino también como una **expresión** capaz de devolver un valor directamente a una variable. == Sintaxis básica con operador flecha (->) == La forma estándar de escribir un ''switch'' utiliza el **operador flecha (->)**. Esta sintaxis tiene dos ventajas clave: * **Ejecución exclusiva:** Solo se ejecuta la instrucción situada a la derecha del caso coincidente (evitando ejecuciones no deseadas). * **Múltiples valores por caso:** Permite agrupar varios valores en la misma línea separándolos por comas. int day = 3; switch (day) { case 1, 2, 3, 4, 5 -> IO.println("Entre semana / Laborable"); case 6, 7 -> IO.println("Fin de semana"); default -> IO.println("Día no válido"); } Si un caso requiere ejecutar más de una instrucción, se delimita mediante un bloque de llaves ''{}'': int option = 1; switch (option) { case 1 -> { IO.println("Iniciando el proceso de copia..."); IO.println("Copia realizada con éxito."); } case 2 -> IO.println("Mostrando configuración..."); default -> IO.println("Opción no reconocida."); } == Switch como expresión (devolución de valores) == Un ''switch'' puede utilizarse directamente a la derecha de una asignación para calcular y **retornar un valor** que se guarda en una variable: int month = 2; String season = switch (month) { case 12, 1, 2 -> "Invierno"; case 3, 4, 5 -> "Primavera"; case 6, 7, 8 -> "Verano"; case 9, 10, 11 -> "Otoño"; default -> "Mes no válido"; }; // Recuerda incluir el punto y coma final por ser una asignación de variable **Exhaustividad obligatoria**: Cuando el ''switch'' se utiliza como expresión para devolver un valor, **debe garantizarse que todos los casos posibles estén cubiertos**. Por este motivo, la cláusula ''default'' es casi siempre obligatoria. == Bloques multilínea y la palabra reservada yield == Cuando utilizamos un ''switch'' como expresión y uno de los casos necesita ejecutar varias líneas dentro de un bloque ''{}'', no podemos devolver el valor directamente tras la flecha. En su lugar, utilizamos la palabra clave **yield** para indicar qué valor retorna ese bloque: int code = 404; String statusText = switch (code) { case 200 -> "OK"; case 404 -> { IO.println("Registrando alerta: recurso no encontrado en el servidor."); yield "Not Found"; // Devuelve este resultado para el caso 404 } case 500 -> "Internal Server Error"; default -> "Unknown Code"; }; == Tipos de datos compatibles == En Java, no todos los tipos de datos pueden ser evaluados dentro de un ''switch''. Los tipos permitidos son: * **Tipos enteros pequeños:** ''byte'', ''short'', ''char'', ''int'' (y sus clases envoltorio/wrapper como ''Integer''). * **Cadenas de texto:** ''String''. * **Tipos enumerados:** ''enum''. String role = "ADMIN"; switch (role) { case "ADMIN", "SUPERUSER" -> IO.println("Acceso total otorgado"); case "USER" -> IO.println("Acceso limitado a lectura"); default -> IO.println("Acceso denegado"); } === Operador ternario === El **operador ternario** (''?:'') es una forma compacta de representar una estructura ''if-else'' simple en una sola línea de código. ^ Sintaxis ^ | ''variable = (condicion) ? valor_si_verdadero : valor_si_falso;'' | Por ejemplo, este bloque condicional: String message; if (age >= 18) { message = "Mayor de edad"; } else { message = "Menor de edad"; } Se simplifica a: String message = (age >= 18) ? "Mayor de edad" : "Menor de edad"; ==== 5.3 Estructuras de iteración (Bucles) ==== Las **iteraciones** o **bucles** permiten repetir un bloque de código mientras una determinada condición sea verdadera. Java proporciona tres estructuras iterativas principales: ''while'', ''do-while'' y ''for''. === El bucle while === Evalúa la condición **antes** de cada iteración. Si es ''true'', ejecuta el bloque de código; si es ''false'', el bucle finaliza. public class BucleWhile { static void main() { int counter = 1; while (counter < 10) { IO.println(counter); counter++; // Incremento obligatorio para evitar bucle infinito } } } **Riesgo de bucle infinito**: Si dentro del cuerpo del bucle no modificamos el valor de las variables que intervienen en la condición (por ejemplo, omitiendo ''counter++''), la condición siempre será ''true'' y el programa quedará atrapado consumiendo recursos. === El bucle do-while === A diferencia del bucle ''while'', el bucle ''do-while'' evalúa la condición **al final** de la iteración. Esto garantiza que el bloque de código se ejecutará **al menos una vez**, independientemente de si la condición es verdadera o falsa desde el inicio. int number = 10; // Se ejecuta 1 vez aunque 10 no es menor que 10 do { IO.println("Bucle do-while: " + number); number++; } while (number < 10); number = 10; // No se ejecuta ninguna vez while (number < 10) { IO.println("Bucle while: " + number); number++; } === El bucle for === Se utiliza principalmente cuando **conocemos de antemano el número exacto de iteraciones** que debemos realizar. Agrupa en una sola línea la inicialización, la condición de parada y el paso de actualización. ^ Estructura de la cabecera for ^ | ''for (inicialización; condición; actualización)'' | public class BucleFor { public static void main(String[] args) { for (int i = 1; i < 10; i++) { IO.println(i); } } } === Control de flujo: break y continue === Las sentencias ''break'' y ''continue'' permiten alterarle el flujo normal de ejecución dentro de cualquier tipo de bucle: * ''break'': Interrumpe y finaliza inmediatamente la ejecución del bucle, saltando a la primera línea fuera de él. * ''continue'': Salta el resto del código de la iteración actual y pasa inmediatamente a evaluar la siguiente iteración. for (int i = 0; i < 10; i++) { if (i > 4) { break; // Detiene el bucle cuando i llega a 5 } IO.println(i); } for (int i = 0; i < 10; i++) { if (i == 4) { continue; // Omite imprimir el valor 4 y continúa con i = 5 } IO.println(i); } === Bucles anidados === Un bucle anidado es un bucle colocado dentro del cuerpo de otro bucle. Por cada iteración del bucle exterior, el bucle interior ejecutará completas todas sus iteraciones. Un ejemplo clásico es la generación de las tablas de multiplicar del 1 al 9: for (int i = 1; i <= 9; i++) { IO.println("--- Tabla del " + i + " ---"); for (int j = 1; j <= 10; j++) { IO.println(i + " x " + j + " = " + (i * j)); } } Aunque es posible anidar múltiples niveles de bucles, el abuso de esta técnica incrementa exponencialmente la complejidad temporal de la aplicación ($O(n^2)$, $O(n^3)$) y dificulta la lectura del código. === ¿Qué bucle elegir? === | Escenario | Bucle recomendado | | Refleja un número conocido de iteraciones | ''for'' | | El número de iteraciones es indefinido, pero debe ejecutarse **al menos una vez** | ''do-while'' | | El número de iteraciones es indefinido y puede que **nunca deba ejecutarse** | ''while'' | ===== 6. Estructuras de datos estáticas ===== ==== 6.1 String ==== Un ''String'' representa una **cadena de caracteres** (texto). Aunque en Java es un objeto, conceptualmente se comporta como una **secuencia de caracteres inmutable** y de tamaño fijo. // Inicialización directa (literal) String nombre = "Ana"; // Mediante la palabra clave new (crea un objeto explícito en memoria) String apellido = new String("García"); === Inmutabilidad === Una propiedad fundamental de los ''String'' es que son **inmutables**: una vez creada la cadena en memoria, **su contenido no se puede modificar**. Cualquier método que apliquemos sobre un ''String'' (convertir a mayúsculas, reemplazar letras, etc.) **no modifica el texto original**, sino que devuelve un **nuevo String** con la transformación. String texto = "hola"; texto.toUpperCase(); // NO modifica la variable 'texto' IO.println(texto); // Muestra "hola" // Para conservar el resultado, debemos reasignarlo a la variable: texto = texto.toUpperCase(); IO.println(texto); // Muestra "HOLA" === Acceso y longitud === A diferencia de los arrays donde usamos corchetes ''[]'' y la propiedad ''.length'', en los ''String'' utilizamos **métodos**: * **Longitud**: ''cadena.length()'' (devuelve el número de caracteres). * **Acceso a un carácter**: ''cadena.charAt(posición)'' (las posiciones van desde **0 hasta length() - 1**). String mensaje = "Java"; IO.println("Longitud: " + mensaje.length()); // Muestra 4 IO.println("Primer carácter: " + mensaje.charAt(0)); // Muestra 'J' IO.println("Último carácter: " + mensaje.charAt(mensaje.length() - 1)); // Muestra 'a' === Comparación de Strings === En Java **NUNCA** debemos usar el operador ''=='' para comparar el contenido de dos cadenas. El operador ''=='' comprueba si ambas variables apuntan a la **misma posición de memoria**, no si contienen las mismas letras. Para comparar texto debemos usar el método **''.equals()''** o **''.equalsIgnoreCase()''** (para ignorar mayúsculas y minúsculas): String texto1 = IO.readln("Introduce una palabra: "); // Ejemplo: "hola" String texto2 = "hola"; // INCORRECTO: Compara referencias de memoria (daría false casi siempre) if (texto1 == texto2) { IO.println("Apuntan al mismo objeto en memoria"); } // CORRECTO: Compara el contenido de las cadenas if (texto1.equals(texto2)) { IO.println("El texto es exactamente igual"); } // Compara ignorando mayúsculas y minúsculas if (texto1.equalsIgnoreCase("HOLA")) { IO.println("Coinciden sin importar mayúsculas/minúsculas"); } === Métodos principales de String === La clase ''String'' dispone de multitud de métodos para manipular texto de forma sencilla: ^ Método ^ Descripción ^ Ejemplo ^ Resultado ^ | ''contains(texto)'' | Comprueba si contiene una subcadena | ''"Programación".contains("grama")'' | ''true'' | | ''indexOf(texto)'' | Devuelve la posición de la primera aparición (o -1 si no existe) | ''"Java".indexOf("v")'' | ''2'' | | ''substring(inicio, fin)'' | Extrae el texto desde ''inicio'' hasta ''fin - 1'' | ''"Elefante".substring(0, 3)'' | ''"Ele"'' | | ''toLowerCase()'' / ''toUpperCase()'' | Convierte todo el texto a minúsculas / mayúsculas | ''"Java".toLowerCase()'' | ''"java"'' | | ''replace(viejo, nuevo)'' | Reemplaza un texto por otro | ''"Casa".replace("a", "o")'' | ''"Coso"'' | | ''trim()'' | Elimina espacios en blanco al inicio y al final | ''" Hola ".trim()'' | ''"Hola"'' | === Recorrido y conversión a char[] === Podemos recorrer un ''String'' carácter a carácter utilizando un bucle ''for'' con ''charAt()'', o convirtiéndolo directamente a un array de caracteres (''char[]''): String palabra = "Algoritmo"; // Opción 1: Recorrido con bucle for tradicional y charAt() for (int i = 0; i < palabra.length(); i++) { char letra = palabra.charAt(i); IO.println("Posición " + i + ": " + letra); } // Opción 2: Convertir a char[] y recorrer con for-each char[] letras = palabra.toCharArray(); for (char letra : letras) { IO.print(letra + " "); } IO.println(""); ==== 6.2 Arrays ==== Los ''array'' permiten almacenar un conjunto finito de elementos del mismo tipo con un tamaño fijo asignado desde su creación. * **Declaración**: ''tipo[] nombre''; * **Creación (instanciación)**: ''nombre = new tipo[tamaño]''; Las posiciones se numeran **desde la 0 hasta tamaño - 1**. Para acceder o modificar un valor usamos los corchetes ''[]''. int[] numbers; numbers = new int[10]; //int[] numbers = new int[10]; Para almacenar valores en cada una de las posiciones del ''array'', usamos corchetes (''[]'') indicando en su interior la posición en la que queremos leer o escribir. numbers[3] = 10; Para acceder a los datos de un ''array'' accedemos mediante ''nombre[posicion]''. int number; number = numbers[3]; === Propiedad length y recorridos === La propiedad ''length'' nos devuelve el número total de elementos. Podemos recorrer el array mediante un bucle ''for'' convencional o con un bucle ''for-each'': // Recorrido con for tradicional for (int i = 0; i < numbers.length; i++) { IO.println("Índice " + i + ": " + numbers[i]); } // Recorrido con for-each (más limpio para lectura) for (int number : numbers) { IO.println(number); } Inicializar ''array'': int[] numbers = new int[5]; for(int i=0; i Otra forma de inicializar un ''array'' es definiendo sus valores como una lista separada por comas encerrada entre llaves. Ésto lo debemos hacer a la hora de la declaración del ''array'', y no hace falta indicarle la longitud, ya que se lo indicamos con el número de elementos que le pasemos: int[] numbers = {1, 3, 312, 15, 69, 7, 8, 9, 0, 1}; === Arrays multidimensionales === Podemos declarar un ''array'' de más de una dimensión (como tablas o matrices): // Inicialización directa int[][] table = { {3, 5, 6}, {2, 4, 9} }; // Recorrido con bucles anidados for (int[] row : table) { for (int cell : row) { IO.print(cell + " "); } IO.println(""); } ===== Ejercicios ===== === Ejercicio 1.a === Crea un programa que calcule el área de un triángulo con base $5$ y altura $10$ ($\text{Área} = \frac{\text{base} \cdot \text{altura}}{2}$). La base y la altura deben ser constantes de tipo entero (''int'') y el área una variable del mismo tipo. === Ejercicio 1.b === Modifica el tipo de la variable área a ''float'' y la altura a $9$. Comprueba si el resultado coincide con el cálculo manual. Haz las modificaciones necesarias en la operación **sin cambiar el tipo de las constantes** para obtener el resultado decimal exacto. === Ejercicio 1.c === Modifica las constantes del Ejercicio 1.a a tipo decimal (''float'' o ''double'') para obtener la solución exacta directamente. === Ejercicio 1.d === Haz que la base y la altura sean introducidas dinámicamente por el usuario a través de teclado mediante ''IO.readln()''. === Ejercicio 2.a === Escribe un programa que solicite dos números decimales (''float'') al usuario e imprima su suma, resta, multiplicación, división y módulo (resto). === Ejercicio 2.b === Modifica el programa anterior para que los resultados de las operaciones se muestren convertidos a entero (''int'') mediante *casting*, sin cambiar el tipo original de las variables de entrada. === Ejercicio 2.c === Utiliza el método de formateo ''String.format("%.2f", valor)'' para mostrar el resultado de la división con exactamente dos decimales. === Ejercicio 3.a === Crea un programa que solicite un número al usuario y calcule su raíz cuadrada empleando el método adecuado de la clase ''Math''. === Ejercicio 3.b === Aplica formato a la salida del ejercicio anterior para presentar la raíz cuadrada con tres decimales. === Ejercicio 4 === Escribe un programa que solicite por teclado una base y un exponente (números enteros) y muestre por pantalla el resultado de elevar la base a dicho exponente empleando la clase ''Math''. Muestra el resultado formateado a 2 decimales. === Ejercicio 5 === Dado el siguiente literal de cadena con espacios superfluos: ''String mensaje = " hola mundo ";'', crea un programa que imprima dicho mensaje completamente en mayúsculas y habiendo eliminado los espacios sobrantes de los extremos. === Ejercicio 6.a === Utiliza la clase ''java.util.Random'' o ''Math.random()'' para generar y mostrar por pantalla un número entero aleatorio entre $0$ y $9$. === Ejercicio 6.b === Modifica el ejercicio anterior para que el usuario introduzca por teclado el límite mínimo y máximo entre los cuales se generará el entero aleatorio. === Ejercicio 7.a === Escribe un programa que pregunte la edad por teclado y muestre el mensaje "Puedes pasar" si es mayor o igual a 18 años, o "No puedes pasar" en caso contrario. Usa ''if-else''. === Ejercicio 7.b === Modifica el ejercicio anterior para resolverlo utilizando únicamente una sentencia ''if'' (sin cláusula ''else''). === Ejercicio 7.c === Reescribe la lógica del ejercicio 1.a utilizando el **operador ternario**. === Ejercicio 8.a === Crea un programa que solicite dos números por teclado y determine cuál de los dos es el mayor. === Ejercicio 8.b === Resuelve el cálculo del mayor de dos números del ejercicio anterior utilizando el operador ternario. === Ejercicio 9.a === Escribe un programa que determine si un número entero introducido por teclado es par o impar utilizando ''if-else''. === Ejercicio 9.b === Modifica el programa anterior para asignar el resultado (''true'' si es par, ''false'' si es impar) a una variable booleana llamada ''isEven'' mediante el operador ternario. === Ejercicio 10.a === Escribe un programa que pida un color por teclado: * Si es "rojo", mostrará: "Color de la sangre". * Si es "azul", mostrará: "Color del cielo". * Si es "amarillo", mostrará: "Color del sol". * Para cualquier otro valor, mostrará: "Lo siento, ese color es muy aburrido"` === Ejercicio 10.b === Modifica el programa anterior para que la comparación no sea sensible a mayúsculas o minúsculas (ej. "Rojo" o "ROJO" funcionarán igual que "rojo"). === Ejercicio 10.c === Añade al programa anterior la limpieza de espacios en blanco al inicio y al final del texto introducido por el usuario (ej. " rojo " debe funcionar correctamente). === Ejercicio 11.a === Escribe un programa que calcule el factorial de un número entero positivo introducido por el usuario utilizando un bucle ''while''. === Ejercicio 11.b === Reescribe el programa del cálculo del factorial utilizando un bucle ''for''. === Ejercicio 12.a === Crea un programa que pida un número entero positivo $N$ por pantalla y muestre todos los números pares comprendidos entre $1$ y $N$ utilizando un bucle ''while''. === Ejercicio 12.b === Reescribe el ejercicio anterior utilizando un bucle ''for''. === Ejercicio 12.c === Modifica el ejercicio anterior utilizando la sentencia ''continue'' dentro del bucle para filtrar los números impares. === Ejercicio 12.d === Formatea la salida del ejercicio anterior para que muestre los números en una sola línea separados por comas: Escribe un número: 17 2, 4, 6, 8, 10, 12, 14, 16 === Ejercicio 13.a === Escribe un programa que encuentre el primer múltiplo de un número dado en un rango (excluyendo el propio número). No utilices la sentencia ''break''. === Ejercicio 13.b === Modifica el programa anterior para detener la búsqueda del múltiplo mediante el uso de la sentencia ''break''. === Ejercicio 14.a === Muestra la tabla de multiplicar de un número introducido por el usuario. === Ejercicio 14.b === Amplía el ejercicio anterior para que, tras cada multiplicación, calcule y muestre el sumatorio acumulado desde 1 hasta el resultado obtenido: Escribe un número: 3 3 * 1 = 3 -> Suma (1..3): 6 3 * 2 = 6 -> Suma (1..6): 21 3 * 3 = 9 -> Suma (1..9): 45 ... === Ejercicio 15.a === Crea un programa que muestre un menú interactivo con 3 opciones y muestre la opción elegida: 1.- Opción A 2.- Opción B 3.- Opción C -------------------- Opción: 2 Ha elegido la Opción B === Ejercicio 15.b === Modifica el menú anterior para añadir la opción "0.- Salir". El programa debe ejecutarse repetidamente y no finalizará hasta que el usuario introduzca el número $0$.