Repaso de Conceptos Fundamentales de Java

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 que main es 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 de main y 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 static son compartidos por todas las instancias de una clase. Si un campo static se 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() o Clase.variableEstatica), eliminando la necesidad de instanciación.
  • Eficiencia de Memoria: Los campos static se 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 static no 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 clave this, ya que this se 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:

  1. Es el punto de entrada de la aplicación; la JVM lo invoca automáticamente al iniciar.
  2. Al ser static, main pertenece a la clase. Esto significa que la JVM no necesita crear un objeto de la clase contenedora para poder ejecutar el método main.
  3. Si se eliminara el modificador static, la JVM no podría invocar directamente main, lo que resultaría en un error de ejecución.
  4. Aunque un método main puede sobrecargarse, la JVM solo reconoce y ejecuta la signatura public static void main(String[] args) como punto de inicio.
  5. Debe ser public para 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, final lo 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 (como String) 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ón printf de C. Utiliza especificadores de formato (ej. %d para enteros, %s para cadenas, %f para 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): Calcula a elevado a la potencia b.
  • 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 (para List), HashSet (para Set).

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 break o continue.
    • 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) o continue (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-catch para cada iteración.

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 LinkedHashSet sí 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() devuelve false).
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 interfaz Deque implementada por ArrayDeque.

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 implementa Queue y Deque) o ArrayDeque (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
       }
   }
   

Etiquetas: java ProgramacionOrientadaAObjetos EstructurasDeDatos ColeccionesJava AlgoritmosMatematicos

Publicado el 8-4 14:37