Skip to content

05. Java Collection Frameworks

Melissa Diaz edited this page Jan 23, 2025 · 1 revision

Java Collection Frameworks

El Framework de Colecciones en Java es una biblioteca integral que ofrece una amplia gama de estructuras de datos y algoritmos diseñados para gestionar conjuntos de datos de manera eficiente. Permiten simplificar y optimizar el código.

Una colección es un objeto que agrupa múltiples elementos para luego manipularlos de manera conjunta. En java se usan para almacenar, organizar y manipular conjuntos de datos de manera eficiente.

Beneficios de Usar Colecciones en Java en Lugar de Arrays

  1. Tamaño Dinámico
    • Crecen o disminuyen según las necesidades, a diferencia de los arrays de tamaño fijo.
  2. Facilidad de Uso
    • Incluyen métodos para agregar, eliminar, buscar y ordenar elementos, simplificando su manejo.
  3. Polimorfismo
    • Permiten usar diferentes tipos de colecciones a través de interfaces comunes como List o Set.
  4. Seguridad de Tipo
    • Con genéricos, garantizan que solo contengan elementos del tipo especificado, evitando errores.
  5. Algoritmos Optimizados
    • Proporcionan métodos eficientes para búsqueda, ordenación y eliminación de duplicados.

Ejemplo:

Array:

int[] numeros = new int[5];
numeros[0] = 10; 

Colección:

List<Integer> numeros = new ArrayList<>();
numeros.add(10); 

Jerarquía de interfaces

El framework proporciona una serie de interfaces, clases y enums que representan diferentes tipos de colecciones y algoritmos para manipularlas.

java collections hierarchy

Algunas de las interfaces principales:

  1. Collection: Define operaciones básicas como agregar, eliminar y recorrer elementos.
  2. List: Colección ordenada que permite duplicados y acceso por índices.
  3. Set: Colección sin duplicados y sin orden específico.
  4. Map: Estructura de llave-valor que asocia claves únicas con valores.

Métodos Más Utilizados en la Clase Collections

Método Tipo de Dato Retornado Descripción
add(E e) boolean Agrega un elemento a la colección.
remove(Object o) boolean Elimina un elemento específico de la colección.
clear() void Remueve todos los elementos de una lista o conjunto.
size() int Devuelve el número de elementos en la colección.
contains(Object o) boolean Verifica si la colección contiene un elemento específico.
reverse(List<T> lista) void Invierte el orden de los elementos en una lista.
reverseOrder() Comparator Retorna un comparator que invierte el orden de los elementos de una colección.
isEmpty() boolean Devuelve true si la colección está vacía.
fill(List<T> lista, Objeto objeto) void Reemplaza todos los elementos de una lista con un elemento específico.
addAll(Collection<? extends E> c) boolean Agrega todos los elementos de otra colección.
removeAll(Collection<?> c) boolean Elimina todos los elementos que también están en la colección especificada.
retainAll(Collection<?> c) boolean Retiene solo los elementos que están contenidos en la colección especificada.
toArray() Object[] Convierte la colección en un array de objetos.
toArray(T[] a) T[] Convierte la colección en un array del tipo especificado.

Note

💡 Retroalimentación en Métodos add y remove
Los métodos add y remove en colecciones devuelven un valor booleano que indica el éxito de la operación:

  • add: Devuelve true si el elemento fue agregado exitosamente.
  • remove: Devuelve true si el elemento fue eliminado con éxito.

Iteración con Iterators

Los Iterators permiten recorrer colecciones de forma secuencial, ofreciendo métodos como:

  • hasNext(): Verifica si hay más elementos.
  • next(): Obtiene el siguiente elemento.

Se prefieren sobre los bucles for o foreach cuando es necesario modificar la colección (agregar o eliminar elementos) durante la iteración, ya que estos bucles no lo permiten.

Interfaz List

La interfaz List representa una colección ordenada secuencialmente que permite duplicados y proporciona un acceso basado en índices a sus elementos. Al igual que los arrays, su índice comienza en 0.

Métodos propios en List:

Método Descripción Tipo de Retorno
void add(int index, E element) Inserta un elemento en la posición especificada de la lista. void
boolean addAll(int index, Collection<? extends E> c) Inserta todos los elementos de una colección en la posición indicada de la lista. boolean
E get(int index) Devuelve el elemento en la posición especificada. E
int indexOf(Object o) Devuelve el índice de la primera ocurrencia del elemento especificado. int
int lastIndexOf(Object o) Devuelve el índice de la última ocurrencia del elemento especificado. int
ListIterator<E> listIterator() Devuelve un iterador de lista para recorrer los elementos. ListIterator<E>
ListIterator<E> listIterator(int index) Devuelve un iterador de lista comenzando en la posición especificada. ListIterator<E>
E remove(int index) Elimina el elemento en la posición especificada de la lista. E
E set(int index, E element) Reemplaza el elemento en la posición especificada por otro elemento. E
void sort(List<T> lista) Ordena los elementos de la lista en orden ascendente. void
void shuffle(List<T> lista) Reorganiza aleatoriamente los elementos de la lista. void
List<E> subList(int fromIndex, int toIndex) Devuelve una vista de la lista entre los índices fromIndex (inclusive) y toIndex (exclusivo). List<E>

ArrayList

El ArrayList es una implementación de la interfaz List en Java, que se basa en arreglos dinámicos para almacenar elementos de manera ordenada. A diferencia de los arreglos estáticos, los ArrayLists pueden crecer o reducirse dinámicamente según sea necesario.

Tiene como beneficios:

  • Capacidad Inicial Personalizable: Permite definir una capacidad inicial para optimizar el rendimiento y evitar reasignaciones frecuentes de memoria.
  • Operaciones Específicas: Soporta métodos como add(index, element) y remove(index) para manipular elementos en posiciones específicas.
  • Acceso Eficiente por Índice: Utiliza un arreglo interno que facilita el acceso rápido a los elementos mediante índices.
  • Soporte para Duplicados: Permite elementos duplicados, a diferencia de implementaciones como Set, que los prohíben.

Ejemplos de Uso de ArrayList

Agregar elementos (add()):

import java.util.ArrayList;

public class App {
    public static void main(String[] args) {
        ArrayList<String> lista = new ArrayList<>();
        lista.add("Manzana");
        lista.add("Banana");
        lista.add("Naranja");
        System.out.println("Lista después de agregar elementos: " + lista);
    }
}

Eliminar elementos (remove()):

import java.util.ArrayList;

public class App {
    public static void main(String[] args) {
        ArrayList<Integer> numeros = new ArrayList<>();
        numeros.add(10);
        numeros.add(20);
        numeros.add(30);

        System.out.println("Lista original: " + numeros);

        numeros.remove(1); // Elimina el elemento en índice 1 (20)
        System.out.println("Lista después de eliminar: " + numeros);
    }
}

Obtener un elemento por índice (get()):

import java.util.ArrayList;

public class App {
    public static void main(String[] args) {
        ArrayList<Double> precios = new ArrayList<>();
        precios.add(10.5);
        precios.add(20.75);
        precios.add(30.0);

        double precio = precios.get(1); // Obtiene el elemento en índice 1 (20.75)
        System.out.println("Precio obtenido: " + precio);
    }
}

Comprobar si está vacío (isEmpty()):

import java.util.ArrayList;

public class App {
    public static void main(String[] args) {
        ArrayList<String> lista = new ArrayList<>();

        if (lista.isEmpty()) {
            System.out.println("La lista está vacía.");
        }

        lista.add("Elemento1");
        System.out.println("Lista actual: " + lista);
        System.out.println("¿Lista vacía? " + lista.isEmpty());
    }
}

Conocer número de elementos (size()):

import java.util.ArrayList;

public class App {
    public static void main(String[] args) {
        ArrayList<String> lista = new ArrayList<>();
        lista.add("Manzana");
        lista.add("Banana");
        lista.add("Naranja");

        System.out.println("Número de elementos en la lista: " + lista.size());
    }
}

Ordenar elementos (sort()):

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

public class App {
    public static void main(String[] args) {
        ArrayList<String> lista = new ArrayList<>();
        lista.add("Banana");
        lista.add("Manzana");
        lista.add("Naranja");

        Collections.sort(lista); // Orden alfabético
        System.out.println("Lista ordenada: " + lista);

        ArrayList<Integer> numeros = new ArrayList<>();
        numeros.add(5);
        numeros.add(2);
        numeros.add(8);

        Collections.sort(numeros); // Ascendente
        System.out.println("Números en orden ascendente: " + numeros);

        Collections.sort(numeros, Collections.reverseOrder()); // Descendente
        System.out.println("Números en orden descendente: " + numeros);
    }
}

Trabajar con objetos (ArrayList de objetos):

Clase Persona:

public class Persona {
    private int documento;
    private String nombre;
    private String apellido;

    public Persona(int documento, String nombre, String apellido) {
        this.documento = documento;
        this.nombre = nombre;
        this.apellido = apellido;
    }

    public int getDocumento() {
        return documento;
    }

    public String getNombre() {
        return nombre;
    }

    public String getApellido() {
        return apellido;
    }

    @Override
    public String toString() {
        return apellido + ", " + nombre + " (" + documento + ")";
    }
}

Clase principal (App):

import java.util.ArrayList;

public class App {
    public static void main(String[] args) {
        Persona p1 = new Persona(111, "Juan", "Pérez");
        Persona p2 = new Persona(222, "Ana", "Gómez");
        Persona p3 = new Persona(333, "Luis", "Martínez");

        ArrayList<Persona> personas = new ArrayList<>();
        personas.add(p1);
        personas.add(p2);
        personas.add(p3);

        System.out.println("Lista inicial:");
        imprimirLista(personas);

        personas.remove(p2); // Eliminar un objeto
        System.out.println("Lista después de eliminar:");
        imprimirLista(personas);
    }

    public static void imprimirLista(ArrayList<Persona> lista) {
        for (Persona p : lista) {
            System.out.println(p);
        }
        System.out.println("-------------------");
    }
}

Note

Documentación oficial

Sets

Los conjuntos son estructuras de datos clave en programación, diseñadas para almacenar elementos únicos, sin duplicados. Son ideales para situaciones donde la singularidad es esencial, como gestionar identificadores únicos, listas de reproducción sin repeticiones, o eliminar duplicados en datos.

¿Qué es una Colección del tipo Set?

Un Set es una colección que garantiza la exclusión de elementos duplicados mediante el método add(), que devuelve false si el elemento ya existe en el conjunto. La unicidad se define a través de los métodos equals() y hashCode(). Dos objetos diferentes pueden considerarse iguales si su implementación de equals() así lo especifica, incluso con atributos distintos.


¿Qué es HashSet?

HashSet es una implementación de la interfaz Set del paquete java.util, basada en tablas hash. A diferencia de otras implementaciones como TreeSet, no mantiene un orden específico en los elementos, pero garantiza la unicidad.

Note

💡 Una tabla hash es una estructura de datos que usa una función hash para calcular un índice basado en una clave, indicando dónde almacenar o buscar un dato en memoria. Esto permite un acceso rápido y eficiente. Si dos claves generan el mismo índice (colisión), se utilizan métodos como listas enlazadas o búsqueda de otro espacio disponible para resolverlo, asegurando su funcionalidad.


Papel en el Framework de Colecciones de Java

  • Unicidad de Elementos: Evita elementos duplicados; no agrega elementos ya presentes.
  • Eficiencia: Operaciones como agregar, eliminar o buscar se realizan en tiempo constante promedio gracias a su implementación basada en tablas hash.
  • No Garantiza Orden: Los elementos no siguen un orden de inserción ni un orden natural.
  • Soporte para Objetos Personalizados: Permite trabajar con objetos personalizados que implementen correctamente hashCode y equals.
  • Casos de Uso: Ideal para verificar pertenencia rápida o eliminar duplicados en grandes conjuntos de datos.

Operaciones básicas de HashSet

  1. Crear un HashSet de enteros:
import java.util.HashSet;

public class EjemploHashSet {
    public static void main(String[] args) {
        // Crear un HashSet vacío de enteros
        HashSet<Integer> numeros = new HashSet<>();
    }
}
  1. Agregar Elementos (add):
numeros.add(10); // Agregar un número al HashSet
numeros.add(20);
numeros.add(30);
System.out.println(numeros); // Salida: [10, 20, 30]
  1. Eliminar Elementos (remove):
numeros.remove(20); // Eliminar el número 20
System.out.println(numeros); // Salida: [10, 30]
  1. Verificar la Existencia (contains):
boolean existe = numeros.contains(10); // Verifica si el número 10 está en el HashSet
System.out.println(existe); // Salida: true
  1. Obtener el Tamaño (size):
int tamano = numeros.size(); // Obtiene el tamaño del HashSet
System.out.println(tamano); // Salida: 2
  1. Iteración a través del HashSet:
for (int numero : numeros) {
    System.out.println(numero); // Itera y muestra cada elemento
}
// Salida:
// 10
// 30

// Iterar a través del HashSet usando un Iterator (como un bucle for-each)
Iterator<Integer> iterador = numeros.iterator();
    while (iterador.hasNext()) {
        int numero = iterador.next();
        System.out.println(numero); // Salida: 10, 30
    }

Funcionamiento Interno de HashSet

HashSet es una implementación de la interfaz Set en Java que utiliza una tabla hash para almacenar elementos de forma única. Los métodos hashCode() y equals() son fundamentales para su funcionamiento:

  1. hashCode(): Calcula un valor hash que se utiliza para determinar la ubicación del elemento en la tabla hash. Este valor debe ser único para optimizar el acceso.
  2. equals(): Comprueba la igualdad lógica entre dos objetos. Cuando se agrega un elemento, primero se calcula su hashCode(). Si ya existe un elemento con el mismo hash, equals() verifica si son iguales, evitando duplicados.

Ejemplo:

import java.util.HashSet;

class Libro {
    private String titulo;
    private String autor;
    private int ISBN;

    public Libro(String titulo, String autor, int ISBN) {
        this.titulo = titulo;
        this.autor = autor;
        this.ISBN = ISBN;
    }

    @Override
    public int hashCode() {
        return ISBN; // El ISBN actúa como identificador único
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        // getClass() obtiene la clase real del objeto en tiempo de ejecución.
        if (obj == null || getClass() != obj.getClass()) return false;
        Libro otroLibro = (Libro) obj;
        return ISBN == otroLibro.ISBN;
    }
}

public class Main {
    public static void main(String[] args) {
        HashSet<Libro> set = new HashSet<>();

        // Crear libros
        Libro libro1 = new Libro("Cien años de soledad", "Gabriel García Márquez", 123456789);
        Libro libro2 = new Libro("El Principito", "Antoine de Saint-Exupéry", 987654321);

        // Agregar libros al HashSet
        set.add(libro1);
        set.add(libro2);

        // Verificar si un libro está presente
        Libro libro3 = new Libro("Cien años de soledad", "Gabriel García Márquez", 123456789);
        System.out.println("¿Contiene libro3?: " + set.contains(libro3)); // true

        // Eliminar un libro
        set.remove(libro2);
        System.out.println("Tamaño del HashSet: " + set.size()); // 1
    }
}
  • Clase Libro: Sobrescribe hashCode() para usar el ISBN como identificador único y equals() para verificar igualdad lógica.
  • Operaciones: - add(): Agrega libros, pero evita duplicados basados en el ISBN. - contains(): Comprueba si un libro con el mismo ISBN ya existe. - remove(): Elimina libros por su ISBN.

Map: HashMap

Un Map en Java es una interfaz que almacena pares clave-valor, donde cada clave es única y está asociada a un único valor. Se utiliza para acceder eficientemente a los valores mediante sus claves. No permite claves duplicadas.

Un HashMap es una implementación de la interfaz Map que utiliza una función hash para calcular un índice, almacenando los pares clave-valor de manera eficiente. Permite un acceso rápido y constante a los valores mediante sus claves.

Un HashMap es ideal para:

  • Asociación de Datos: Asociar rápidamente claves a valores, como configuraciones o cachés.
  • Eficiencia en Búsquedas: Operaciones como put, get, y remove tienen tiempo promedio constante O(1)O(1).
  • Asociaciones Uno a Uno: Garantiza que cada clave tiene exactamente un valor único asociado.

Operaciones Básicas de HashMap

Método Descripción
put(K key, V value) Agrega o actualiza un par clave-valor. Devuelve el valor anterior o null si la clave es nueva.
get(Object key) Recupera el valor asociado a una clave. Devuelve null si no existe la clave.
remove(Object key) Elimina un par clave-valor. Devuelve el valor eliminado o null si no existe.
containsKey(Object key) Verifica si una clave está presente en el mapa.
size() Retorna el número de pares clave-valor.
isEmpty() Retorna true si el mapa está vacío.
clear() Elimina todos los pares clave-valor del mapa.
values() Devuelve una colección de los valores almacenados.
replace(K key, V value) Reemplaza el valor asociado con una clave. Devuelve el valor anterior o null si no existe.

Ejemplo de uso:

import java.util.HashMap;

public class EjemploHashMap {
    public static void main(String[] args) {
        // Crear un HashMap
        HashMap<String, Integer> inventario = new HashMap<>();

        // Agregar elementos
        inventario.put("Manzanas", 50);
        inventario.put("Naranjas", 30);
        inventario.put("Peras", 20);

        // Obtener un valor
        System.out.println("Cantidad de Naranjas: " + inventario.get("Naranjas")); // 30

        // Reemplazar un valor
        inventario.replace("Manzanas", 60);
        System.out.println("Cantidad de Manzanas actualizada: " + inventario.get("Manzanas")); // 60

        // Verificar si contiene una clave
        System.out.println("¿Contiene 'Peras'? " + inventario.containsKey("Peras")); // true

        // Eliminar un elemento
        inventario.remove("Naranjas");
        System.out.println("Inventario después de eliminar Naranjas: " + inventario);

        // Obtener el tamaño
        System.out.println("Tamaño del inventario: " + inventario.size()); // 2

        // Iterar sobre el HashMap
        System.out.println("Inventario:");
        for (String clave : inventario.keySet()) {
            System.out.println(clave + ": " + inventario.get(clave));
        }
    }
}

Salida:

Cantidad de Naranjas: 30
Cantidad de Manzanas actualizada: 60
¿Contiene 'Peras'? true
Inventario después de eliminar Naranjas: {Manzanas=60, Peras=20}
Tamaño del inventario: 2
Inventario:
Manzanas: 60
Peras: 20

Clone this wiki locally