Comprensión de public static void main(String[] args)
La declaración del método main es el punto de inicio de cualquier aplicación Java. Cada palabra clave y componente tiene un propósito específico:
public: Este modificador de acceso permite que el método sea invocado desde cualquier lugar, lo cual es esencial para que la Máquina Virtual de Java (JVM) pueda acceder a él.static: Indica quemaines un método de clase, no de instancia. Esto significa que puede ser llamado directamente usando el nombre de la clase, sin necesidad de crear un objeto de dicha clase. La JVM no necesita instanciar su clase para ejecutar el programa.void: Especifica que el método no devuelve ningún valor. La JVM simplemente ejecuta las instrucciones dentro demainy no espera un resultado de vuelta.main: Este es el nombre estándar y convencional para el método de punto de entrada. La JVM busca específicamente un método con este nombre para comenzar la ejecución.String[] args: Representa un array de cadenas de texto que permite al programa aceptar argumentos desde la línea de comandos cuando se ejecuta.
El Modificador static
En Java, el modificador static se aplica a miembros de una clase (variables o métodos) para indicar que pertenecen a la clase misma, en lugar de a cualquier instancia particular de la clase. Esto implica que se pueden acceder directamente a través del nombre de la clase, sin necesidad de crear un objeto.
Propósito y Ventajas
- Asociación a la Clase: Los miembros
staticson compartidos por todas las instancias de una clase. Si un campostaticse modifica, el cambio es visible para todas las instancias. - Acceso Directo: Permite invocar métodos o acceder a variables utilizando el nombre de la clase (por ejemplo,
Clase.metodoEstatico()oClase.variableEstatica), eliminando la necesidad de instanciación. - Eficiencia de Memoria: Los campos
staticse cargan una única vez en la memoria cuando la clase se carga, lo que puede ahorrar memoria en comparación con los campos de instancia que se replican para cada objeto.
Restricciones
- Un método
staticno puede acceder directamente a variables de instancia (no estáticas) ni a métodos de instancia. - Dentro de un método
static, no se puede usar la palabra clavethis, ya quethisse refiere a la instancia actual del objeto.
Rol de static en el Método main
El método main debe ser static por las siguientes razones clave:
- Es el punto de entrada de la aplicación; la JVM lo invoca automáticamente al iniciar.
- Al ser
static,mainpertenece a la clase. Esto significa que la JVM no necesita crear un objeto de la clase contenedora para poder ejecutar el métodomain. - Si se eliminara el modificador
static, la JVM no podría invocar directamentemain, lo que resultaría en un error de ejecución. - Aunque un método
mainpuede sobrecargarse, la JVM solo reconoce y ejecuta la signaturapublic static void main(String[] args)como punto de inicio. - Debe ser
publicpara que la JVM pueda acceder a él.
El Modificador final
El modificador final en Java se utiliza para definir una entidad que no puede ser modificada, heredada o sobrescrita. Su uso aporta claridad, seguridad y puede optimizar el rendimiento:
- Variables
final: Una vez asignado un valor, no puede ser cambiado. Esto asegura la inmutabilidad y mejora la legibilidad al indicar que un valor es constante. Aunque se pueda "garantizar" manualmente que una variable no cambie,finallo impone a nivel de compilación. - Métodos
final: No pueden ser sobrescritos por subclases. Esto se usa para prevenir comportamientos inesperados en jerarquías de herencia o para optimizaciones (el compilador puede realizar "inlining"). - Clases
final: No pueden ser heredadas por ninguna otra clase. Esto se utiliza para crear clases inmutables (comoString) o para asegurar la integridad de su implementación.
El uso de final no solo añade una restricción sintáctica, sino que también comunica la intención del desarrollador y permite al compilador realizar ciertas optimizaciones, como la inlining de valores constantes.
Funciones Matemáticas Comunes: Máximo Común Divisor, Mínimo Común Múltiplo y Primalidad
A continuación se presentan implementaciones de algoritmos fundamentales para el trabajo con números enteros:
public class UtilidadesNumericas {
/**
* Calcula el Máximo Común Divisor (MCD) de dos números enteros utilizando el algoritmo de Euclides.
* El algoritmo se basa en la propiedad de que MCD(a, b) = MCD(b, a mod b).
*
* @param numeroUno Primer número entero.
* @param numeroDos Segundo número entero.
* @return El MCD de los dos números.
*/
public static int calcularMCD(int numeroUno, int numeroDos) {
while (numeroDos != 0) {
int residuo = numeroUno % numeroDos;
numeroUno = numeroDos;
numeroDos = residuo;
}
return numeroUno;
}
/**
* Calcula el Mínimo Común Múltiplo (MCM) de dos números enteros.
* Se utiliza la relación MCM(a, b) = (a * b) / MCD(a, b).
*
* @param numeroUno Primer número entero.
* @param numeroDos Segundo número entero.
* @return El MCM de los dos números.
*/
public static int calcularMCM(int numeroUno, int numeroDos) {
if (numeroUno == 0 || numeroDos == 0) return 0;
return Math.abs(numeroUno * numeroDos) / calcularMCD(numeroUno, numeroDos);
}
/**
* Determina si un número entero es primo.
* Un número primo es un número natural mayor que 1 que no tiene divisores positivos
* más que 1 y él mismo.
*
* @param valor Numero a verificar.
* @return true si el número es primo, false en caso contrario.
*/
public static boolean esPrimo(int valor) {
if (valor < 2) {
return false;
}
// Solo necesitamos verificar divisores hasta la raíz cuadrada del número.
// Si un número tiene un divisor mayor que su raíz cuadrada,
// entonces también debe tener uno menor que su raíz cuadrada.
for (int i = 2; i * i <= valor; i++) {
if (valor % i == 0) {
return false;
}
}
return true;
}
}
Interfaces en Java
Una interfaz en Java es un contrato para clases. Define un conjunto de métodos (sin implementación) que una clase que implemente la interfaz debe proporcionar. Esto promueve la abstracción y el polimorfismo.
// Definición de una interfaz: ComportamientoSonoro
interface ComportamientoSonoro {
void emitirSonido(); // Los métodos de interfaz son implícitamente public y abstract
}
// Clase Perro que implementa la interfaz ComportamientoSonoro
class Perro implements ComportamientoSonoro {
@Override
public void emitirSonido() {
System.out.println("Guau guau");
}
}
// Clase Gato que implementa la interfaz ComportamientoSonoro
class Gato implements ComportamientoSonoro {
@Override
public void emitirSonido() {
System.out.println("Miau miau");
}
}
¿Cuándo usar Métodos static o de Instancia?
La elección entre métodos static y de instancia depende de si el método necesita acceder o modificar el estado de un objeto específico.
- Métodos
static:- Pertenecen a la clase, no a un objeto.
- Se invocan usando el nombre de la clase (por ejemplo,
Clase.metodoEstatico()). - Ideales para funcionalidades que no dependen de los datos de un objeto, como utilidades matemáticas (
Math.sqrt()), métodos de fábrica (Integer.valueOf()) o el punto de entrada principal (main). - No pueden acceder a variables de instancia ni a métodos no estáticos directamente.
- Métodos de Instancia (no
static):- Pertenecen a un objeto específico de la clase.
- Se invocan a través de una instancia de la clase (por ejemplo,
objeto.metodoDeInstancia()). - Son necesarios cuando el método opera sobre los datos propios de un objeto, como los métodos de comportamiento de un
Perro(emitirSonido()) o métodos para modificar el estado interno. - Pueden acceder a variables de instancia, métodos de instancia y métodos estáticos.
Importante: Los métodos de implementación de una interfaz (como emitirSonido()) son por naturaleza métodos de instancia. Siempre deben ser declarados como no static en las clases que implementan la interfaz, ya que su comportamiento dependerá de la instancia específica que implementa el contrato.
Entrada y Salida en Java
Clase Scanner para Entrada de Datos
La clase Scanner es una herramienta versátil para leer datos primitivos y cadenas desde diversas fuentes, como el sistema de entrada estándar (teclado).
| Método | Descripción |
|---|---|
nextTipoDato() |
Lee el siguiente token de entrada como el tipo de dato especificado (ej. nextInt() para un entero, nextDouble() para un doble). |
next() |
Lee el siguiente token de entrada como una cadena. La lectura se detiene al encontrar un delimitador (por defecto, espacio en blanco, tabulador o salto de línea). |
nextLine() |
Lee toda la línea de entrada hasta el siguiente delimitador de línea. Es útil para leer frases completas que contienen espacios. |
Consideración importante al usar Scanner: Cuando se utiliza un método como nextInt() o nextDouble() para leer un valor numérico, estos métodos leen el número pero no consumen el carácter de salto de línea (\n) que se genera al presionar Enter. Si posteriormente se llama a nextLine(), este leerá inmediatamente ese \n pendiente, resultando en una cadena vacía. Para evitar esto, es común añadir una llamada extra a scanner.nextLine() después de leer un número para "consumir" el salto de línea residual.
import java.util.Scanner;
public class EjemploEntrada {
public static void main(String[] args) {
Scanner teclado = new Scanner(System.in);
System.out.print("Introduce tu edad: ");
int edad = teclado.nextInt(); // Lee el número, deja el '\n' en el buffer
// Consumir el '\n' restante para evitar problemas con nextLine()
teclado.nextLine();
System.out.print("Introduce tu nombre completo: ");
String nombre = teclado.nextLine(); // Ahora leerá correctamente el nombre
System.out.println("Hola, " + nombre + ". Tienes " + edad + " años.");
teclado.close();
}
}
Salida de Datos
Para mostrar información en la consola, Java ofrece varios métodos en la clase System.out:
System.out.print(valor): Imprime el valor especificado sin añadir un salto de línea al final.System.out.println(valor): Imprime el valor y luego añade un salto de línea, moviendo el cursor a la siguiente línea.System.out.printf(formato, args...): Permite una salida formateada, similar a la funciónprintfde C. Utiliza especificadores de formato (ej.%dpara enteros,%spara cadenas,%fpara flotantes).
public class EjemploSalida {
public static void main(String[] args) {
String producto = "Laptop";
double precio = 1250.75;
int cantidad = 2;
System.out.print("El producto es: ");
System.out.println(producto);
System.out.printf("Precio unitario: %.2f EUR, Cantidad: %d%n", precio, cantidad);
System.out.printf("Total: %.2f EUR%n", precio * cantidad);
}
}
Métodos Matemáticos Comunes con la Clase Math
La clase Math de Java proporciona un conjunto de métodos estáticos para realizar operaciones matemáticas básicas. Al ser estáticos, se pueden invocar directamente usando el nombre de la clase:
Math.max(a, b): Retorna el mayor de dos valores.Math.min(a, b): Retorna el menor de dos valores.Math.abs(a): Retorna el valor absoluto de un número.Math.sqrt(a): Calcula la raíz cuadrada de un número.Math.pow(a, b): Calculaaelevado a la potenciab.Math.round(a): Redondea un número flotante al entero más cercano.
public class OperacionesMatematicas {
public static void main(String[] args) {
double valor1 = 10.5;
double valor2 = 7.3;
int exponente = 3;
System.out.println("Máximo entre " + valor1 + " y " + valor2 + ": " + Math.max(valor1, valor2));
System.out.println("Valor absoluto de -15: " + Math.abs(-15));
System.out.println("Raíz cuadrada de 64: " + Math.sqrt(64));
System.out.println(valor1 + " elevado a la " + exponente + ": " + Math.pow(valor1, exponente));
}
}
Conversión entre Bases Numéricas
La clase Integer en Java facilita la conversión de números entre la base decimal y otras bases (binario, octal, hexadecimal, etc.), y viceversa.
De Decimal a Otras Bases
El método Integer.toString(int numero, int base) convierte un número entero decimal a su representación en una base numérica específica.
public class ConversorBases {
public static void main(String[] args) {
int valorDecimal = 125; // Número en base 10
// Convertir a binario (base 2)
String binario = Integer.toString(valorDecimal, 2);
System.out.println(valorDecimal + " en binario es: " + binario); // Salida: "1111101"
// Convertir a octal (base 8)
String octal = Integer.toString(valorDecimal, 8);
System.out.println(valorDecimal + " en octal es: " + octal); // Salida: "175"
// Convertir a hexadecimal (base 16)
String hexadecimal = Integer.toString(valorDecimal, 16);
System.out.println(valorDecimal + " en hexadecimal es: " + hexadecimal); // Salida: "7d"
// Convertir a base 5
String baseCinco = Integer.toString(valorDecimal, 5);
System.out.println(valorDecimal + " en base 5 es: " + baseCinco); // Salida: "1000"
}
}
De Otras Bases a Decimal
El método Integer.parseInt(String representacion, int base) interpreta una cadena como un número en una base dada y lo convierte a su valor entero decimal.
public class ConversorBasesADecimal {
public static void main(String[] args) {
// De binario a decimal
int desdeBinario = Integer.parseInt("10110", 2);
System.out.println("Binario \"10110\" a decimal: " + desdeBinario); // Salida: 22
// De octal a decimal
int desdeOctal = Integer.parseInt("37", 8);
System.out.println("Octal \"37\" a decimal: " + desdeOctal); // Salida: 31
// De base 4 a decimal
int desdeBaseCuatro = Integer.parseInt("321", 4);
System.out.println("Base 4 \"321\" a decimal: " + desdeBaseCuatro); // Salida: 57
// De hexadecimal a decimal
int desdeHexadecimal = Integer.parseInt("F3A", 16);
System.out.println("Hexadecimal \"F3A\" a decimal: " + desdeHexadecimal); // Salida: 3898
}
}
Conversión de Mayúsculas y Minúsculas
En Java, existen varias formas de manipular el caso de caracteres y cadenas de texto.
Parra Cadenas de Caracteres (String)
La clase String ofrece métodos convenientes para convertir toda una cadena a minúsculas o mayúsculas.
public class ManipulacionCadena {
public static void main(String[] args) {
String textoOriginal = "Hola Mundo Java";
// Convertir a minúsculas
String textoMinusculas = textoOriginal.toLowerCase();
System.out.println("Minúsculas: " + textoMinusculas); // Salida: hola mundo java
// Convertir a mayúsculas
String textoMayusculas = textoOriginal.toUpperCase();
System.out.println("Mayúsculas: " + textoMayusculas); // Salida: HOLA MUNDO JAVA
}
}
Para Caracteres Individuales (char)
La clase Character proporciona métodos estáticos para la conversión de un solo carácter. Además, se puede utilizar una técnica de manipulación de bits.
public class ManipulacionCaracter {
public static void main(String[] args) {
char letraMinuscula = 'x';
char letraMayuscula = 'Y';
// Usando métodos de Character
char aMayuscula = Character.toUpperCase(letraMinuscula);
char aMinuscula = Character.toLowerCase(letraMayuscula);
System.out.println(letraMinuscula + " a mayúscula: " + aMayuscula); // Salida: X
System.out.println(letraMayuscula + " a minúscula: " + aMinuscula); // Salida: y
// Conversión utilizando operador XOR (^) y el valor 32 (0x20)
// Esto funciona porque la diferencia entre mayúsculas y minúsculas en ASCII/Unicode es 32 (el quinto bit).
// XOR con 32 invierte este bit, cambiando el caso.
char charOriginal = 'c';
char charTransformado = (char) (charOriginal ^ 32); // 'c' (99) ^ 32 (32) = 'C' (67)
System.out.println("'" + charOriginal + "' XOR 32 -> '" + charTransformado + "'"); // Salida: 'c' XOR 32 -> 'C'
char charOriginal2 = 'K';
char charTransformado2 = (char) (charOriginal2 ^ 32); // 'K' (75) ^ 32 (32) = 'k' (107)
System.out.println("'" + charOriginal2 + "' XOR 32 -> '" + charTransformado2 + "'"); // Salida: 'K' XOR 32 -> 'k'
}
}
Genéricos en Java
Los genéricos son una característica poderosa en Java que permite definir clases, interfaces y métodos con tipos de datos "placeholders" que se especifican en el momento de su uso. Su propósito principal es mejorar la seguridad de tipos en tiempo de compilación y eliminar la necesidad de casts explícitos.
- Seguridad de Tipos: Ayudan a detectar errores de tipo en tiempo de compilación en lugar de en tiempo de ejecución, reduciendo la probabilidad de
ClassCastException. - Reutilización de Código: Permiten escribir algoritmos que funcionan con diferentes tipos de objetos, sin comprometer la seguridad de tipos.
- Clairdad: Hacen el código más legible y auto-documentado al indicar claramente los tipos de datos con los que operan las colecciones o métodos.
Al trabajar con objetos en colecciones genéricas y al comparar su igualdad, es fundamental utilizar el método .equals() en lugar del operador ==, ya que == compara referencias de objetos (si son el mismo objeto en memoria) mientras que .equals() compara el contenido o el estado de los objetos.
Colecciones en Java
Las colecciones son estructuras de datos que permiten almacenar, organizar y manipular grupos de objetos de manera eficiente. Java ofrece un robusto framework de Colecciones que incluye interfaces (como List, Set, Map) y sus diversas implementaciones (como ArrayList, HashSet, HashMap).
Colecciones de un solo elemento (Collection)
La interfaz Collection es la raíz de la jerarquía de colecciones de un solo elemento. Sus subinterfaces principales son List y Set.
- Interfaces:
Collection,List,Set. - Clases de Implementación Comunes:
ArrayList(paraList),HashSet(paraSet).
Métodos Comunes de la Interfaz Collection
| Método | Descripción |
|---|---|
public int size() |
Devuelve el número de elementos en la colección. Útil para verificar si está vacía o para condiciones de bucle. |
public boolean isEmpty() |
Retorna true si la colección no contiene elementos; de lo contrario, false. |
public boolean contains(Object obj) |
Retorna true si la colección contiene el objeto especificado. |
public boolean add(E e) |
Añade el elemento especificado a la colección. |
public boolean remove(Object obj) |
Elimina una única instancia del elemento especificado de esta colección, si está presente. |
public void clear() |
Elimina todos los elementos de la colección. |
public Object[] toArray() |
Devuelve un array que contiene todos los elementos de la colección en el orden adecuado (si la colección tiene un orden). |
Recorrido de Colecciones
Existen varias maneras de iterar sobre los elementos de una colección:
1. Usando un Iterador (Iterator)
El iterador proporciona una forma estándar de recorrer y eliminar elementos de una colección.
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteradorEjemplo {
public static void main(String[] args) {
Collection<String> frutas = new ArrayList<>();
frutas.add("Manzana");
frutas.add("Banana");
frutas.add("Cereza");
Iterator<String> iteradorFrutas = frutas.iterator();
while (iteradorFrutas.hasNext()) {
String frutaActual = iteradorFrutas.next();
System.out.println("Fruta: " + frutaActual);
}
}
}
2. Bucle for-each (Bucle For Mejorado)
Una sintaxis más concisa para iterar sobre colecciones y arrays.
import java.util.ArrayList;
import java.util.List;
public class ForEachEjemplo {
public static void main(String[] args) {
List<Integer> numeros = new ArrayList<>();
numeros.add(10);
numeros.add(20);
numeros.add(30);
for (int num : numeros) {
System.out.println("Número: " + num);
}
}
}
3. Expresiones Lambda con forEach
Desde Java 8, la interfaz Iterable (implementada por Collection) incluye el método forEach que acepta una expresión lambda.
import java.util.ArrayList;
import java.util.List;
public class LambdaForEachEjemplo {
public static void main(String[] args) {
List<String> nombres = new ArrayList<>();
nombres.add("Alice");
nombres.add("Bob");
nombres.add("Charlie");
// Usando expresión lambda
nombres.forEach(nombre -> System.out.println("Nombre: " + nombre));
// Usando referencia a método (más conciso)
System.out.println("--- Nombres con referencia a método ---");
nombres.forEach(System.out::println);
}
}
Cuándo usar forEach (Lambda) vs. Bucle for-each
- Utilice
forEach(Lambda) cuando:- La lógica de procesamiento es simple y concisa.
- No necesita controlar el flujo del bucle con
breakocontinue. - Quiere aprovechar la concisión de las expresiones lambda o referencias a métodos.
- Está trabajando con Streams de Java 8 para procesamiento paralelo.
- Utilice el Bucle
for-each(mejorado) cuando:- Necesita la capacidad de
break(para salir del bucle) ocontinue(para saltar a la siguiente iteración). - La lógica dentro del bucle es más compleja y puede requerir un bloque de código más extenso.
- Necesita manejar excepciones específicas dentro del bucle con
try-catchpara cada iteración.
- Necesita la capacidad de
Listas (List)
La interfaz List representa una colección ordenada de elementos, donde cada elemento tiene un índice (posición). Permite elementos duplicados.
- Propósito: Ofrece una alternativa a los arrays de tamaño fijo, permitiendo arrays dinámicos.
- Implementación Común:
ArrayList, que es una implementación basada en un array redimensionable. - Características:
- Ordenada: Mantiene el orden de inserción de los elementos.
- Indexada: Los elementos se acceden por su índice numérico (0 a
size() - 1). - Permite Duplicados: Se pueden añadir múltiples veces el mismo objeto.
Métodos Específicos de List
Además de los métodos de Collection, List añade:
| Método | Descripción |
|---|---|
E get(int index) |
Devuelve el elemento en la posición especificada en esta lista. |
void add(int index, E element) |
Inserta el elemento especificado en la posición indicada en esta lista. |
E remove(int index) |
Elimina el elemento en la posición especificada en esta lista. |
import java.util.ArrayList;
import java.util.List;
public class EjemploLista {
public static void main(String[] args) {
List<String> animales = new ArrayList<>();
animales.add("León");
animales.add("Tigre");
animales.add("Elefante");
System.out.println("Primer animal: " + animales.get(0)); // Salida: León
animales.add(1, "Cebra"); // Insertar en la posición 1
System.out.println("Lista después de insertar: " + animales); // Salida: [León, Cebra, Tigre, Elefante]
}
}
Conjuntos (Set)
La interfaz Set representa una colección de elementos que no contiene duplicados. Modeliza el concepto matemático de un conjunto.
- Propósito: Almacenar elementos únicos y realizar operaciones de conjunto (unión, intersección, diferencia).
- Implementación Común:
HashSet, que utiliza una tabla hash para un acceso rápido. - Características:
- Sin Orden Garentizado: Generalmente no mantiene el orden de inserción (aunque
LinkedHashSetsí lo hace). - Sin Índices: No se puede acceder a elementos por índice.
- Sin Duplicados: Si se intenta añadir un elemento que ya existe, la operación de adición se ignora o falla (dependiendo de la implementación,
add()devuelvefalse).
- Sin Orden Garentizado: Generalmente no mantiene el orden de inserción (aunque
import java.util.HashSet;
import java.util.Set;
public class EjemploConjunto {
public static void main(String[] args) {
Set<String> colores = new HashSet<>();
colores.add("Rojo");
colores.add("Verde");
colores.add("Azul");
colores.add("Rojo"); // Este duplicado será ignorado
System.out.println("Colores en el conjunto: " + colores); // Salida: [Rojo, Azul, Verde] (orden puede variar)
System.out.println("¿Contiene 'Verde'? " + colores.contains("Verde")); // Salida: true
}
}
Colecciones de Dos Elementos (Map)
La interfaz Map almacena pares clave-valor, donde cada clave es única y se mapea a un valor. No se considera una Collection directamente.
- Propósito: Asociar un valor con una clave única para una recuperación eficiente.
- Implementación Común:
HashMap, que utiliza una tabla hash para almacenar y acceder a los pares. - Características:
- Claves Únicas: Cada clave debe ser única. Si se inserta un par con una clave existente, el valor anterior se sobrescribe.
- Sin Orden Garantizado: Generalmente no mantiene el orden de inserción de las claves.
- Acceso Rápido: Permite recuperar valores rápidamente utilizando su clave.
Métodos Comunes de la Interfaz Map
| Método | Descripción |
|---|---|
V put(K key, V value) |
Asocia el valor especificado con la clave especificada en este mapa. Si la clave ya existía, el valor anterior es reemplazado. |
V get(Object key) |
Devuelve el valor al que se mapea la clave especificada, o null si el mapa no contiene un mapeo para la clave. |
int size() |
Devuelve el número de pares clave-valor en este mapa. |
Set<Map.Entry<K, V>> entrySet() |
Devuelve un Set de los pares clave-valor contenidos en este mapa. Útil para iterar sobre todos los elementos. |
V getOrDefault(Object key, V defaultValue) |
Devuelve el valor al que se mapea la clave especificada, o defaultValue si el mapa no contiene un mapeo para la clave. |
import java.util.HashMap;
import java.util.Map;
public class EjemploMapa {
public static void main(String[] args) {
Map<String, String> capitales = new HashMap<>();
capitales.put("España", "Madrid");
capitales.put("Francia", "París");
capitales.put("Alemania", "Berlín");
System.out.println("Capital de España: " + capitales.get("España")); // Salida: Madrid
System.out.println("Capital de Italia (si no existe, 'Desconocida'): " + capitales.getOrDefault("Italia", "Desconocida")); // Salida: Desconocida
// Iterar sobre el mapa
for (Map.Entry<String, String> entrada : capitales.entrySet()) {
System.out.println("País: " + entrada.getKey() + ", Capital: " + entrada.getValue());
}
}
}
Pilas (Stack)
Una pila es una estructura de datos abstracta que sigue el principio LIFO (Last In, First Out - Último en Entrar, Primero en Salir). Los elementos se añaden y se eliminan del mismo extremo, llamado "cima" o "tope" de la pila.
- Características: LIFO.
- Implementación Común: La clase
Stack(legado) o, preferentemente, la interfazDequeimplementada porArrayDeque.
Métodos Comunes
| Método | Descripción |
|---|---|
void push(E element) |
Añade un elemento a la cima de la pila. |
E pop() |
Elimina y devuelve el elemento de la cima de la pila. Lanza EmptyStackException si la pila está vacía (o NoSuchElementException con Deque). |
E peek() |
Devuelve el elemento de la cima de la pila sin eliminarlo. Lanza EmptyStackException si la pila está vacía (o NoSuchElementException con Deque). |
import java.util.ArrayDeque;
import java.util.Deque;
public class EjemploPila {
public static void main(String[] args) {
// Se recomienda usar Deque como interfaz para pilas modernas
Deque<Integer> pilaNumeros = new ArrayDeque<>();
pilaNumeros.push(10); // push() para añadir a la cima
pilaNumeros.push(20);
pilaNumeros.push(30);
System.out.println("Elemento en la cima (sin eliminar): " + pilaNumeros.peek()); // Salida: 30
System.out.println("Eliminando de la cima: " + pilaNumeros.pop()); // Salida: 30
System.out.println("Elemento en la nueva cima: " + pilaNumeros.peek()); // Salida: 20
}
}
Colas (Queue)
Una cola es una estructura de datos abstracta que sigue el principio FIFO (First In, First Out - Primero en Entrar, Primero en Salir). Los elementos se añaden por un extremo (la "parte trasera" o "cola") y se eliminan por el otro (la "parte delantera" o "cabeza").
- Características: FIFO.
- Implementación Común:
LinkedList(que implementaQueueyDeque) oArrayDeque(para una implementación basada en array).
Métodos Comunes
| Método | Descripción |
|---|---|
boolean add(E element) |
Añade un elemento a la cola. Lanza IllegalStateException si la capacidad de la cola es limitada. |
boolean offer(E element) |
Añade un elemento a la cola. Retorna false si la capacidad de la cola es limitada y no se puede añadir. Preferible a add para colas de capacidad limitada. |
E remove() |
Recupera y elimina la cabeza de la cola. Lanza NoSuchElementException si la cola está vacía. |
E poll() |
Recupera y elimina la cabeza de la cola. Retorna null si la cola está vacía. Preferible a remove. |
E element() |
Recupera la cabeza de la cola sin eliminarla. Lanza NoSuchElementException si la cola está vacía. |
E peek() |
Recupera la cabeza de la cola sin eliminarla. Retorna null si la cola está vacía. Preferible a element. |
import java.util.LinkedList;
import java.util.Queue;
public class EjemploCola {
public static void main(String[] args) {
Queue<String> clientes = new LinkedList<>();
clientes.offer("Cliente A"); // offer() es preferible a add()
clientes.offer("Cliente B");
clientes.offer("Cliente C");
System.out.println("Primer cliente en la cola (sin eliminar): " + clientes.peek()); // Salida: Cliente A
System.out.println("Atendiendo al cliente: " + clientes.poll()); // Salida: Cliente A
System.out.println("Primer cliente restante: " + clientes.peek()); // Salida: Cliente B
clientes.add("Cliente D"); // También se puede usar add(), pero lanza excepción si la cola está llena.
System.out.println("Cola actual: " + clientes);
}
}
Ordenamiento de Arrays y Colecciones
Java proporciona utilidades para ordenar arrays y colecciones de manera eficiente.
Ordenamiento de Arrays
La clase estática Arrays ofrece métodos para ordenar arrays de tipos primitivos y objetos.
import java.util.Arrays;
public class OrdenamientoArrays {
public static void main(String[] args) {
int[] arregloNumeros = {5, 2, 8, 1, 9, 3};
System.out.println("Array original: " + Arrays.toString(arregloNumeros));
// Ordenar de forma ascendente (por defecto)
Arrays.sort(arregloNumeros);
System.out.println("Array ordenado (ascendente): " + Arrays.toString(arregloNumeros)); // Salida: [1, 2, 3, 5, 8, 9]
// Ordenar arrays de objetos (ej. Integer) con un comparador
Integer[] arregloObjetos = {50, 20, 80, 10, 90, 30};
// Ordenar de forma descendente usando una expresión lambda
Arrays.sort(arregloObjetos, (a, b) -> b - a);
System.out.println("Array de objetos (descendente): " + Arrays.toString(arregloObjetos)); // Salida: [90, 80, 50, 30, 20, 10]
}
}
Ordenamiento de Colecciones (List)
La clase estática Collections proporciona métodos para ordenar elementos en colecciones que implementan la interfaz List.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class OrdenamientoColecciones {
public static void main(String[] args) {
List<String> listaNombres = new ArrayList<>();
listaNombres.add("Carlos");
listaNombres.add("Ana");
listaNombres.add("Beto");
listaNombres.add("Diana");
System.out.println("Lista original: " + listaNombres);
// Ordenar de forma ascendente (alfabéticamente por defecto para String)
Collections.sort(listaNombres);
System.out.println("Lista ordenada (ascendente): " + listaNombres); // Salida: [Ana, Beto, Carlos, Diana]
// Ordenar con un comparador personalizado (ej. descendente)
List<Integer> listaNumeros = new ArrayList<>();
listaNumeros.add(7);
listaNumeros.add(1);
listaNumeros.add(9);
listaNumeros.add(4);
System.out.println("Lista de números original: " + listaNumeros);
// Ordenar de forma descendente usando una expresión lambda
Collections.sort(listaNumeros, (n1, n2) -> n2 - n1);
System.out.println("Lista de números (descendente): " + listaNumeros); // Salida: [9, 7, 4, 1]
}
}
Operador Módulo (%)
El operador módulo (%) en Java devuelve el resto de una división. Es una herramienta muy versátil en programación para diversas tareas.
1. Determinación de Números Pares o Impares
Si el resto de la división de un número entre 2 es 0, el número es par; de lo contrario, es impar.
public class ParImpar {
public static void main(String[] args) {
int numero = 17;
if (numero % 2 == 0) {
System.out.println(numero + " es un número par.");
} else {
System.out.println(numero + " es un número impar.");
}
}
}
2. Ejecución Periódica de Tareas
Útil para realizar una acción cada N iteraciones en un bucle.
public class TareasPeriodicas {
public static void main(String[] args) {
for (int i = 1; i <= 15; i++) {
if (i % 3 == 0) {
System.out.println("Iteración " + i + ": Ejecutar tarea cada 3 iteraciones.");
} else {
System.out.println("Iteración " + i + ": Continuar.");
}
}
}
}
3. Mapeo de Valores a un Rango Específico (Hashing)
En algoritmos de hashing, el operador módulo se usa comúnmente para mapear un valor hash a un índice dentro de un array o tabla de tamaño fijo.
public class MapeoHash {
public static void main(String[] args) {
int valorHash = 12345;
int tamanoTabla = 100; // La tabla tiene índices de 0 a 99
// Mapea el valor hash a un índice dentro del rango [0, tamanoTabla-1]
int indice = valorHash % tamanoTabla;
System.out.println("El valor hash " + valorHash + " se mapea al índice " + indice); // Salida: 45
}
}