Ir al contenido principal

Colecciones en Java: List, Set y Map

Introducción

Las colecciones son estructuras de datos que nos permiten almacenar y manipular grupos de objetos de forma eficiente en Java. A diferencia de los arrays, que tienen un tamaño fijo, las colecciones son dinámicas y ofrecen numerosos métodos para realizar operaciones comunes como agregar, eliminar y buscar elementos. El framework de colecciones de Java proporciona interfaces y clases que implementan distintos tipos de colecciones, cada una con características específicas que la hacen adecuada para diferentes necesidades.

En este artículo, exploraremos las tres interfaces principales de colecciones en Java: List, Set y Map. Aprenderemos cómo funcionan, cuándo utilizar cada una y cómo implementarlas en nuestros programas para resolver problemas prácticos de manera elegante y eficiente.

List: colecciones ordenadas con elementos duplicados

Las listas son colecciones ordenadas que permiten elementos duplicados. Mantienen el orden de inserción (a menos que se especifique lo contrario) y permiten acceder a los elementos mediante un índice, similar a los arrays.

Principales implementaciones de List

Java ofrece varias implementaciones de la interfaz List, cada una con características específicas:

  • ArrayList: Implementación basada en arrays dinámicos, ideal para acceso aleatorio.
  • LinkedList: Implementación basada en listas doblemente enlazadas, eficiente para inserciones y eliminaciones.
  • Vector: Similar a ArrayList pero sincronizado (thread-safe).

Ejemplo básico de ArrayList

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

public class EjemploArrayList {
    public static void main(String[] args) {
        // Creación de un ArrayList de cadenas
        List<String> frutas = new ArrayList<>();
        
        // Añadir elementos
        frutas.add("Manzana");
        frutas.add("Plátano");
        frutas.add("Naranja");
        frutas.add("Manzana");  // Se permite duplicados
        
        // Mostrar todos los elementos
        System.out.println("Lista de frutas: " + frutas);
        
        // Acceder a un elemento por su índice
        System.out.println("Fruta en posición 1: " + frutas.get(1));
        
        // Tamaño de la lista
        System.out.println("Número de frutas: " + frutas.size());
        
        // Comprobar si contiene un elemento
        System.out.println("¿Contiene Naranja? " + frutas.contains("Naranja"));
        
        // Eliminar un elemento
        frutas.remove("Plátano");
        System.out.println("Lista después de eliminar: " + frutas);
        
        // Recorrer la lista con bucle for-each
        System.out.println("Recorriendo la lista:");
        for (String fruta : frutas) {
            System.out.println("- " + fruta);
        }
    }
}

Ejemplo de LinkedList

import java.util.LinkedList;

public class EjemploLinkedList {
    public static void main(String[] args) {
        LinkedList<Integer> numeros = new LinkedList<>();
        
        // Añadir elementos al final
        numeros.add(10);
        numeros.add(20);
        
        // Añadir al principio
        numeros.addFirst(5);
        
        // Añadir al final (igual que add)
        numeros.addLast(30);
        
        System.out.println("Lista enlazada: " + numeros);
        
        // Obtener primer y último elemento
        System.out.println("Primer elemento: " + numeros.getFirst());
        System.out.println("Último elemento: " + numeros.getLast());
        
        // Eliminar primer y último elemento
        numeros.removeFirst();
        numeros.removeLast();
        
        System.out.println("Después de eliminar: " + numeros);
    }
}

Set: colecciones sin elementos duplicados

Los conjuntos (Sets) son colecciones que no permiten elementos duplicados. A diferencia de las listas, los sets no garantizan un orden específico (excepto algunas implementaciones).

Principales implementaciones de Set

  • HashSet: Implementación basada en tablas hash, muy eficiente para operaciones básicas.
  • LinkedHashSet: Mantiene el orden de inserción, combinando tablas hash con listas enlazadas.
  • TreeSet: Implementación basada en árboles, mantiene los elementos ordenados.

Ejemplo de HashSet

import java.util.HashSet;
import java.util.Set;

public class EjemploHashSet {
    public static void main(String[] args) {
        // Crear un HashSet de cadenas
        Set<String> lenguajes = new HashSet<>();
        
        // Añadir elementos
        lenguajes.add("Java");
        lenguajes.add("Python");
        lenguajes.add("JavaScript");
        lenguajes.add("Java");  // Intento de duplicado (será ignorado)
        
        // Mostrar el conjunto
        System.out.println("Lenguajes de programación: " + lenguajes);
        
        // Comprobar si contiene un elemento
        System.out.println("¿Contiene Java? " + lenguajes.contains("Java"));
        
        // Tamaño del conjunto
        System.out.println("Número de lenguajes: " + lenguajes.size());
        
        // Eliminar un elemento
        lenguajes.remove("Python");
        System.out.println("Después de eliminar Python: " + lenguajes);
        
        // Recorrer el conjunto
        System.out.println("Recorriendo el conjunto:");
        for (String lenguaje : lenguajes) {
            System.out.println("- " + lenguaje);
        }
    }
}

Ejemplo de TreeSet

import java.util.TreeSet;

public class EjemploTreeSet {
    public static void main(String[] args) {
        TreeSet<Integer> numeros = new TreeSet<>();
        
        // Añadir elementos en cualquier orden
        numeros.add(10);
        numeros.add(5);
        numeros.add(20);
        numeros.add(7);
        
        // TreeSet mantiene los elementos ordenados
        System.out.println("Números ordenados: " + numeros);
        
        // Obtener el primer y último elemento
        System.out.println("Elemento más pequeño: " + numeros.first());
        System.out.println("Elemento más grande: " + numeros.last());
        
        // Obtener subconjuntos
        System.out.println("Números menores que 10: " + numeros.headSet(10));
        System.out.println("Números desde 7 hasta 20: " + numeros.subSet(7, true, 20, true));
    }
}

Map: colecciones de pares clave-valor

Los mapas son colecciones que almacenan pares clave-valor, donde cada clave es única. No extienden de la interfaz Collection, pero se consideran parte del framework de colecciones de Java.

Principales implementaciones de Map

  • HashMap: Implementación basada en tablas hash, muy eficiente.
  • LinkedHashMap: Mantiene el orden de inserción de las claves.
  • TreeMap: Mantiene las claves ordenadas según su orden natural o un comparador.

Ejemplo de HashMap

import java.util.HashMap;
import java.util.Map;

public class EjemploHashMap {
    public static void main(String[] args) {
        // Crear un HashMap que asocia nombres con edades
        Map<String, Integer> edades = new HashMap<>();
        
        // Añadir pares clave-valor
        edades.put("Carlos", 25);
        edades.put("Ana", 22);
        edades.put("Miguel", 30);
        
        // Mostrar el mapa
        System.out.println("Mapa de edades: " + edades);
        
        // Obtener un valor a partir de su clave
        System.out.println("Edad de Ana: " + edades.get("Ana"));
        
        // Si la clave no existe, devuelve null
        System.out.println("Edad de Juan: " + edades.get("Juan"));
        
        // Comprobar si existe una clave
        System.out.println("¿Existe Miguel? " + edades.containsKey("Miguel"));
        
        // Comprobar si existe un valor
        System.out.println("¿Hay alguien con 30 años? " + edades.containsValue(30));
        
        // Recorrer el mapa
        System.out.println("Recorriendo el mapa:");
        for (Map.Entry<String, Integer> entrada : edades.entrySet()) {
            System.out.println(entrada.getKey() + " tiene " + entrada.getValue() + " años");
        }
        
        // Otra forma de recorrer solo las claves
        System.out.println("Nombres en el mapa:");
        for (String nombre : edades.keySet()) {
            System.out.println("- " + nombre);
        }
    }
}

Ejemplo de TreeMap

import java.util.TreeMap;

public class EjemploTreeMap {
    public static void main(String[] args) {
        TreeMap<String, String> capitales = new TreeMap<>();
        
        // Añadir pares clave-valor
        capitales.put("España", "Madrid");
        capitales.put("Francia", "París");
        capitales.put("Italia", "Roma");
        capitales.put("Alemania", "Berlín");
        
        // TreeMap mantiene las claves ordenadas
        System.out.println("Capitales ordenadas por país: " + capitales);
        
        // Obtener la primera y última entrada
        System.out.println("Primera entrada: " + capitales.firstEntry());
        System.out.println("Última entrada: " + capitales.lastEntry());
        
        // Obtener subconjuntos
        System.out.println("Países desde 'A' hasta 'F': " + 
                           capitales.subMap("A", true, "F", true));
    }
}

Operaciones comunes con colecciones

Java proporciona la clase Collections que contiene métodos estáticos para realizar operaciones comunes en colecciones:

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

public class OperacionesColecciones {
    public static void main(String[] args) {
        List<Integer> numeros = new ArrayList<>();
        numeros.add(5);
        numeros.add(2);
        numeros.add(8);
        numeros.add(1);
        
        System.out.println("Lista original: " + numeros);
        
        // Ordenar una lista
        Collections.sort(numeros);
        System.out.println("Lista ordenada: " + numeros);
        
        // Invertir una lista
        Collections.reverse(numeros);
        System.out.println("Lista invertida: " + numeros);
        
        // Barajar elementos (aleatorizar)
        Collections.shuffle(numeros);
        System.out.println("Lista barajada: " + numeros);
        
        // Valor máximo y mínimo
        System.out.println("Valor máximo: " + Collections.max(numeros));
        System.out.println("Valor mínimo: " + Collections.min(numeros));
    }
}

Cuándo usar cada tipo de colección

Para elegir el tipo de colección adecuado, considera estas directrices:

  • Usa List cuando:

    • Necesites mantener el orden de los elementos
    • Necesites acceder a elementos por su posición
    • Permites elementos duplicados
  • Usa Set cuando:

    • No quieras elementos duplicados
    • No te importe el orden (HashSet) o necesites un orden específico (TreeSet, LinkedHashSet)
    • Necesites operaciones de conjunto (unión, intersección, etc.)
  • Usa Map cuando:

    • Necesites asociar valores con claves únicas
    • Necesites búsquedas rápidas por clave
    • Quieras organizar datos en formato clave-valor

Ejemplo práctico: agenda de contactos

Veamos un ejemplo que integra diferentes tipos de colecciones para implementar una agenda de contactos:

import java.util.*;

public class AgendaContactos {
    public static void main(String[] args) {
        // Mapa para almacenar contactos (nombre -> teléfono)
        Map<String, String> contactos = new HashMap<>();
        
        // Lista para mantener el historial de llamadas
        List<String> historialLlamadas = new ArrayList<>();
        
        // Conjunto para categorías sin duplicados
        Set<String> categorias = new HashSet<>();
        
        // Añadir contactos
        contactos.put("María", "666111222");
        contactos.put("Juan", "655333444");
        contactos.put("Pablo", "644555666");
        
        // Añadir categorías
        categorias.add("Amigos");
        categorias.add("Trabajo");
        categorias.add("Familia");
        categorias.add("Amigos");  // Duplicado, será ignorado
        
        // Simular algunas llamadas
        simularLlamada("María", historialLlamadas);
        simularLlamada("Juan", historialLlamadas);
        simularLlamada("María", historialLlamadas);
        
        // Mostrar información
        System.out.println("--- AGENDA DE CONTACTOS ---");
        System.out.println("Contactos: " + contactos);
        System.out.println("Categorías: " + categorias);
        System.out.println("Historial de llamadas: " + historialLlamadas);
        
        // Buscar un contacto
        String nombre = "María";
        if (contactos.containsKey(nombre)) {
            System.out.println("\nTeléfono de " + nombre + ": " + contactos.get(nombre));
        } else {
            System.out.println("\nContacto no encontrado");
        }
        
        // Contar llamadas por contacto
        Map<String, Integer> frecuenciaLlamadas = new HashMap<>();
        for (String contacto : historialLlamadas) {
            frecuenciaLlamadas.put(contacto, 
                                  frecuenciaLlamadas.getOrDefault(contacto, 0) + 1);
        }
        
        System.out.println("\nFrecuencia de llamadas:");
        for (Map.Entry<String, Integer> entrada : frecuenciaLlamadas.entrySet()) {
            System.out.println(entrada.getKey() + ": " + entrada.getValue() + " llamadas");
        }
    }
    
    // Método para simular una llamada
    private static void simularLlamada(String contacto, List<String> historial) {
        historial.add(contacto);
        System.out.println("Llamando a " + contacto + "...");
    }
}

Resumen

Las colecciones en Java proporcionan estructuras de datos flexibles y potentes para almacenar y manipular grupos de objetos. En este artículo hemos explorado las tres interfaces principales:

  • List: Colecciones ordenadas que permiten duplicados, ideales cuando el orden es importante.
  • Set: Colecciones que no permiten elementos duplicados, perfectas para representar conjuntos matemáticos.
  • Map: Estructuras clave-valor donde cada clave es única, excelentes para búsquedas y asociaciones.

Dominar el framework de colecciones es esencial para desarrollar aplicaciones Java eficientes, ya que te permite elegir la estructura de datos más adecuada para cada problema. En el siguiente artículo, exploraremos cómo trabajar con fechas y horas en Java usando el paquete java.time, lo que complementará tu conocimiento sobre las API estándar de Java más utilizadas.