Colecciones en Java — ejercicios con autores, catálogos y una fusión de bibliotecas (y la «b» que estaba en el mapa pero no aparecía)

Conté las letras de un título con un Map<Character, Integer>, imprimí el mapa y allí estaban todas, bien visibles, incluida la b. Después le pregunté al mapa cuántas b tenía con letras.get("b") y me respondió null. Sin error, sin aviso, sin nada. Revisé el bucle de conteo tres veces antes de darme cuenta de que el problema no estaba en lo que guardaba, sino en lo que preguntaba: las claves eran char, y yo buscaba un String.

Las colecciones tienen un puñado de trampas así, silenciosas y que compilan. Hoy practicamos con tres programas de dificultad creciente (el último, una fusión de dos bibliotecas), tres fragmentos para depurar y un chuletario para llevarte.

Estos ejercicios usan la teoría de colecciones y repasan lo de la práctica. Sin ellas puedes hacerlos, pero te costará más.

Cómo usar este artículo

  1. Lee el enunciado de un nivel y no mires la solución todavía.
  2. Escribe tu versión, compílala y compara tu salida con la esperada.
  3. Si te atascas, mira solo la 🔵 pista y vuelve a intentarlo.
  4. Cuando termines, compara con la solución y fíjate en por qué está hecha así, no solo en que dé la misma salida.

Ningún ejercicio usa Scanner. Los datos van fijos en el código para que tu salida sea idéntica a la esperada y puedas comprobarla de un vistazo.

Nivel 1 · Básico: tres colecciones para la misma biblioteca

Enunciado. Usa este record para representar los libros (un record es una clase de datos con constructor, getters, equals() y hashCode() ya hechos):

public record Libro(String isbn, String titulo, String autor) {
    @Override
    public String toString() {
        return titulo + " (" + autor + ")";
    }
}

En una clase N1 con main, crea una lista con estos ocho libros:

ISBNTítuloAutor
L-101DuneHerbert
L-102EmmaAusten
L-103HamletShakespeare
L-104MacbethShakespeare
L-105Rebeccadu Maurier
L-106MiseryKing
L-107CarrieKing
L-108ItKing

Recórrela una sola vez y, durante ese recorrido, rellena (a) un conjunto con los autores distintos, que se muestre ordenado, y (b) un mapa que permita encontrar cada libro por su ISBN. Después imprime el número de libros de la lista, los autores, y consulta dos ISBN: L-103, que existe, y L-999, que no.

Salida esperada:

Libros en la lista: 8
Autores distintos (ordenados): [Austen, Herbert, King, Shakespeare, du Maurier]
L-103 -> Hamlet (Shakespeare)
L-999 -> no existe

Para que el conjunto salga ordenado, elige el sabor adecuado de Set (¿cuál ordena?). Para el mapa, la clave es el ISBN y el valor, el Libro entero. Antes de pedir un valor con get, comprueba con containsKey si la clave existe: así evitas imprimir null.

Nivel 2 · Intermedio: agrupar los libros por autor

Enunciado. Con los mismos ocho libros, construye en N2 un mapa autor → lista de títulos. Los autores deben salir en orden alfabético y los títulos de cada autor en el orden en que están en la lista original. Imprime una línea por autor y, al final, el autor con más libros y cuántos tiene.

Salida esperada:

Austen: [Emma]
Herbert: [Dune]
King: [Misery, Carrie, It]
Shakespeare: [Hamlet, Macbeth]
du Maurier: [Rebecca]
Más libros: King (3)

El valor del mapa es una List<String>, y la primera vez que ves un autor esa lista todavía no existe. Para crearla solo cuando hace falta hay un método de Map que lo hace en una línea: computeIfAbsent(clave, k -> new ArrayList<>()) te devuelve la lista (la crea si es nueva) y a continuación haces .add(...). Para el máximo, recorre entrySet() guardando el mejor visto hasta ahora.

Nivel 3 · Desafío: fusionar dos bibliotecas

Enunciado. Dos bibliotecas guardan su inventario como Map<String, Integer> (ISBN → ejemplares). Escribe una clase Fusion con cuatro métodos estáticos:

  • fusionar(a, b): devuelve un mapa ordenado por ISBN con la suma de ejemplares de ambas bibliotecas.
  • enComun(a, b): el conjunto ordenado de ISBN que están en las dos.
  • soloEn(a, b): el conjunto ordenado de ISBN que están en a pero no en b.
  • total(m): la suma de todos los ejemplares de un mapa.

Ninguno de los métodos puede modificar los mapas que recibe. Para probar tu clase, usa este main (clase N3):

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

public class N3 {
    public static void main(String[] args) {
        Map<String, Integer> centro = new HashMap<>();   // ISBN -> ejemplares
        centro.put("L-101", 3);
        centro.put("L-102", 1);
        centro.put("L-103", 2);
        centro.put("L-105", 1);

        Map<String, Integer> barrio = new HashMap<>();
        barrio.put("L-102", 2);
        barrio.put("L-103", 1);
        barrio.put("L-104", 4);
        barrio.put("L-107", 1);

        System.out.println("Fusionado:   " + Fusion.fusionar(centro, barrio));
        System.out.println("En común:    " + Fusion.enComun(centro, barrio));
        System.out.println("Solo centro: " + Fusion.soloEn(centro, barrio));
        System.out.println("Solo barrio: " + Fusion.soloEn(barrio, centro));
        System.out.println("Ejemplares en total: " + Fusion.total(Fusion.fusionar(centro, barrio)));
        System.out.println("El centro sigue igual: " + centro);
    }
}

Salida esperada:

Fusionado:   {L-101=3, L-102=3, L-103=3, L-104=4, L-105=1, L-107=1}
En común:    [L-102, L-103]
Solo centro: [L-101, L-105]
Solo barrio: [L-104, L-107]
Ejemplares en total: 15
El centro sigue igual: {L-105=1, L-101=3, L-102=1, L-103=2}

keySet() no devuelve una copia, sino una vista del mapa: si le quitas elementos al conjunto, se los quitas al mapa. Haz una copia antes de modificar nada: new TreeSet<>(a.keySet()). Para «en común» y «solo en» no necesitas bucles: retainAll y removeAll hacen la intersección y la diferencia. Para sumar, merge(clave, valor, Integer::sum).

¿Qué falla aquí?

Tres fragmentos con un fallo cada uno. Antes de leer la explicación, haz dos cosas: predice si compila y, si compila, qué imprime (o qué pasa al ejecutarlo).

Fragmento 1

F1.java

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

public class F1 {
    public static void main(String[] args) {
        // Números de socio con algún préstamo vencido
        List<Integer> socios = new ArrayList<>(List.of(10, 20, 30, 1));
        socios.remove(1);   // quiero quitar al socio número 1
        System.out.println("Socios: " + socios);
    }
}

Piénsalo antes de seguir: ¿compila? ¿qué imprime? Quiero quitar al socio número 1.

Explicación. Compila y se ejecuta sin errores, pero quita al socio equivocado:

Socios: [10, 30, 1]

List tiene dos métodos remove: remove(int index), que borra por posición, y remove(Object o), que borra por valor. Con un List<Integer>, la llamada socios.remove(1) pasa un int literal y Java elige el primero: borra el elemento de la posición 1, que es el 20. El 1 que querías quitar sigue ahí.

Llamar a remove(n) en una List<Integer> esperando borrar por valor. Si el argumento es un int, Java entiende posición. No avisa, no lanza excepción: simplemente borra otra cosa.

Arreglo. Para que Java entienda «por valor», pásale un Integer (un objeto), no un int:

// Números de socio con algún préstamo vencido
List<Integer> socios = new ArrayList<>(List.of(10, 20, 30, 1));
socios.remove(Integer.valueOf(1));   // por valor: quita al socio número 1
System.out.println("Socios: " + socios);
Socios: [10, 20, 30]

Con List<Integer>, ten siempre claro si quieres quitar por posición (remove(2)) o por valor (remove(Integer.valueOf(2))). Con listas de String u otros objetos no hay duda: remove("Dune") siempre es por valor.

Fragmento 2

F2.java

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

public class F2 {
    public static void main(String[] args) {
        String titulo = "Macbeth";
        Map<Character, Integer> letras = new HashMap<>();
        for (char c : titulo.toCharArray()) {
            letras.merge(c, 1, Integer::sum);
        }
        System.out.println("Letras de " + titulo + ": " + letras);
        System.out.println("¿Cuántas 'b' tiene? " + letras.get("b"));
    }
}

*¿Compila? ¿qué imprime? El mapa tiene las letras de Macbeth y quiero saber cuántas b hay.*

Explicación. Compila y no lanza ninguna excepción, pero la respuesta es falsa:

Letras de Macbeth: {a=1, b=1, c=1, t=1, e=1, h=1, M=1}
¿Cuántas 'b' tiene? null

El mapa sí tiene la b (aparece en la primera línea). Pero sus claves son Character, y letras.get("b") pregunta por el String "b", que es una clave distinta. Y get acepta un Object, así que el compilador no protesta: simplemente no encuentra nada y devuelve null. El mismo problema aparece con containsKey, remove o contains en un Set.

Buscar en un Map o un Set con un tipo distinto del de las claves ("b" en vez de 'b', un long en vez de un int…). No hay error de compilación ni excepción: solo un null o un false que no esperabas.

Arreglo. Pregunta con el mismo tipo que las claves ('b', un char entre comillas simples, que Java convierte en Character):

System.out.println("¿Cuántas 'b' tiene? " + letras.get('b'));
Letras de Macbeth: {a=1, b=1, c=1, t=1, e=1, h=1, M=1}
¿Cuántas 'b' tiene? 1

Si el mapa es Map<Character, Integer>, todas las consultas llevan char con comillas simples. Una buena costumbre: cuando un get devuelva null sin motivo, mira primero el tipo de lo que le pasas.

Fragmento 3

Libro.java (el mismo record del nivel 1)

F3.java

import java.util.Set;
import java.util.TreeSet;

public class F3 {
    public static void main(String[] args) {
        Set<Libro> catalogo = new TreeSet<>();
        try {
            catalogo.add(new Libro("L-101", "Dune", "Herbert"));
            catalogo.add(new Libro("L-102", "Emma", "Austen"));
        } catch (ClassCastException e) {
            System.out.println("Excepción: " + e.getMessage());
        }
        System.out.println("Catálogo: " + catalogo);
    }
}

¿Compila? ¿qué imprime? Quiero el catálogo ordenado automáticamente.

Explicación. Compila, pero al ejecutarlo falla en cuanto se añade el primer libro:

Excepción: class Libro cannot be cast to class java.lang.Comparable (Libro is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
Catálogo: []

TreeSet mantiene los elementos ordenados, y para ordenarlos necesita saber cómo comparar dos Libro. Un record no es Comparable por defecto, y el compilador no avisa (el conjunto acepta cualquier tipo): el fallo aparece en ejecución, como ClassCastException. Fíjate en que ni siquiera se llega a añadir el primer libro: el catálogo queda vacío.

Meter objetos propios en un TreeSet o usarlos como clave de un TreeMap sin que sean Comparable y sin dar un Comparator. Es el mismo contrato de la teoría de Comparable, solo que aquí lo exige la colección.

Arreglo. Dale al TreeSet un Comparator que diga por qué campo ordenar:

Set<Libro> catalogo = new TreeSet<>(Comparator.comparing(Libro::titulo));
catalogo.add(new Libro("L-101", "Dune", "Herbert"));
catalogo.add(new Libro("L-102", "Emma", "Austen"));
catalogo.add(new Libro("L-103", "Dune", "Otra edición"));   // mismo título: el árbol lo cree repetido
System.out.println("Catálogo: " + catalogo);
Catálogo: [Dune (Herbert), Emma (Austen)]

Un TreeSet decide si dos elementos son «el mismo» con el comparador, no con equals(). Por eso la tercera edición de Dune (mismo título, otro autor) no se añade: para el comparador, «Dune» y «Dune» empatan. Elige el criterio de orden con cuidado.

Soluciones

Mira esta sección solo cuando hayas intentado el ejercicio. Si tu código es distinto pero da la misma salida y cumple los requisitos, también es una solución válida.

Solución del Nivel 1

N1.java

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

public class N1 {
    public static void main(String[] args) {
        List<Libro> libros = List.of(
                new Libro("L-101", "Dune", "Herbert"),
                new Libro("L-102", "Emma", "Austen"),
                new Libro("L-103", "Hamlet", "Shakespeare"),
                new Libro("L-104", "Macbeth", "Shakespeare"),
                new Libro("L-105", "Rebecca", "du Maurier"),
                new Libro("L-106", "Misery", "King"),
                new Libro("L-107", "Carrie", "King"),
                new Libro("L-108", "It", "King"));

        Set<String> autores = new TreeSet<>();
        Map<String, Libro> porIsbn = new HashMap<>();
        for (Libro l : libros) {
            autores.add(l.autor());       // un conjunto no guarda repetidos
            porIsbn.put(l.isbn(), l);     // clave: ISBN, valor: el libro entero
        }

        System.out.println("Libros en la lista: " + libros.size());
        System.out.println("Autores distintos (ordenados): " + autores);
        for (String isbn : new String[]{"L-103", "L-999"}) {
            if (porIsbn.containsKey(isbn)) {
                System.out.println(isbn + " -> " + porIsbn.get(isbn));
            } else {
                System.out.println(isbn + " -> no existe");
            }
        }
    }
}

Por qué está hecho así:

  • Un único for-each sobre la lista rellena las dos colecciones a la vez: no hace falta recorrer la lista dos veces.
  • autores es un TreeSet: no guarda repetidos (los tres libros de King dejan una sola entrada) y se imprime ordenado. Como son String, el orden es el natural, y por eso du Maurier queda al final: las minúsculas van detrás de las mayúsculas.
  • porIsbn es un HashMap<String, Libro>: la clave es el ISBN y el valor, el libro entero. Buscar por clave no depende del tamaño del mapa.
  • containsKey antes de get evita imprimir un null. También valdría getOrDefault, pero entonces el mensaje «no existe» tendría que ser un Libro inventado.
  • List.of(...) crea una lista que no se puede modificar, lo cual aquí es una ventaja: nadie la toca por error. Si quisieras añadir libros, tendría que ser new ArrayList<>(List.of(...)).

Solución del Nivel 2

N2.java

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class N2 {
    public static void main(String[] args) {
        List<Libro> libros = List.of(
                new Libro("L-101", "Dune", "Herbert"),
                new Libro("L-102", "Emma", "Austen"),
                new Libro("L-103", "Hamlet", "Shakespeare"),
                new Libro("L-104", "Macbeth", "Shakespeare"),
                new Libro("L-105", "Rebecca", "du Maurier"),
                new Libro("L-106", "Misery", "King"),
                new Libro("L-107", "Carrie", "King"),
                new Libro("L-108", "It", "King"));

        Map<String, List<String>> porAutor = new TreeMap<>();
        for (Libro l : libros) {
            porAutor.computeIfAbsent(l.autor(), k -> new ArrayList<>()).add(l.titulo());
        }

        String masLibros = null;
        for (Map.Entry<String, List<String>> e : porAutor.entrySet()) {
            System.out.println(e.getKey() + ": " + e.getValue());
            if (masLibros == null || e.getValue().size() > porAutor.get(masLibros).size()) {
                masLibros = e.getKey();
            }
        }
        System.out.println("Más libros: " + masLibros + " (" + porAutor.get(masLibros).size() + ")");
    }
}

Por qué está hecho así:

  • TreeMap ordena las claves, así que los autores salen alfabéticamente sin hacer nada más.
  • computeIfAbsent(autor, k -> new ArrayList<>()) hace tres cosas en una: busca la clave, crea la lista vacía solo si no existía y la devuelve. El .add(...) se aplica a esa lista, que ya está dentro del mapa.
  • Las listas conservan el orden de inserción, así que los tres de King salen como en la lista original: Misery, Carrie, It.
  • Para el máximo, masLibros empieza en null y se actualiza solo si el autor actual tiene estrictamente más libros. Con un empate, ganaría el primero alfabéticamente.
  • porAutor.get(masLibros).size() se puede usar con seguridad porque masLibros es una clave que sabemos que existe.

Solución del Nivel 3

Fusion.java

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

public class Fusion {

    public static Map<String, Integer> fusionar(Map<String, Integer> a, Map<String, Integer> b) {
        Map<String, Integer> resultado = new TreeMap<>(a);                  // copia de a, ordenada
        b.forEach((isbn, n) -> resultado.merge(isbn, n, Integer::sum));     // suma lo de b
        return resultado;
    }

    public static Set<String> enComun(Map<String, Integer> a, Map<String, Integer> b) {
        Set<String> comunes = new TreeSet<>(a.keySet());   // copia: keySet() es una "vista" de a
        comunes.retainAll(b.keySet());
        return comunes;
    }

    public static Set<String> soloEn(Map<String, Integer> a, Map<String, Integer> b) {
        Set<String> solo = new TreeSet<>(a.keySet());
        solo.removeAll(b.keySet());
        return solo;
    }

    public static int total(Map<String, Integer> m) {
        int suma = 0;
        for (int n : m.values()) {
            suma += n;
        }
        return suma;
    }
}

Por qué está hecho así:

  • new TreeMap<>(a) hace una copia de a, ordenada por clave. Así fusionar no toca el mapa original.
  • b.forEach((isbn, n) -> ...) recorre cada pareja de b; merge suma el valor si el ISBN ya estaba en el resultado, o lo crea si no.
  • enComun y soloEn empiezan con new TreeSet<>(a.keySet()): una copia de las claves. Sin esa copia, retainAll y removeAll actuarían sobre la vista y **borrarían claves de a**. La última línea de la salida (El centro sigue igual) lo comprueba.
  • retainAll(b.keySet()) conserva solo lo que está también en b (intersección); removeAll(b.keySet()) quita lo que está en b (diferencia).
  • total recorre values() con un for-each. El int n recibe cada Integer por unboxing automático.
  • Los números cuadran: el centro tiene 3+1+2+1 = 7 ejemplares y el barrio 2+1+4+1 = 8, así que el total es 15. L-102 pasa a 3 (1+2) y L-103 también (2+1).

Míralo paso a paso en Python Tutor

El fallo del fragmento 1 es perfecto para verlo en vivo: la diferencia entre borrar «por posición» y borrar «por valor». Entra en pythontutor.com/java.html, elige Java y pega este programa. Es lo mismo que hacen las dos versiones de remove, pero con un array y con métodos propios, para que veas cada paso:

public class Tutor {

    static int quitarPorPosicion(int[] a, int n, int pos) {
        for (int i = pos; i < n - 1; i++) {
            a[i] = a[i + 1];                  // cada elemento retrocede un hueco
        }
        return n - 1;
    }

    static int buscar(int[] a, int n, int valor) {
        for (int i = 0; i < n; i++) {
            if (a[i] == valor) {
                return i;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        int[] socios = {10, 20, 30, 1};
        int n = 4;
        n = quitarPorPosicion(socios, n, 1);   // lo que hace remove(1) en una lista de enteros
        System.out.println("Por posición: " + socios[0] + " " + socios[1] + " " + socios[2]);

        int[] otros = {10, 20, 30, 1};
        int m = 4;
        int pos = buscar(otros, m, 1);          // primero localizo el valor 1...
        m = quitarPorPosicion(otros, m, pos);   // ...y luego lo quito
        System.out.println("Por valor:    " + otros[0] + " " + otros[1] + " " + otros[2]);
    }
}

Salida esperada:

Por posición: 10 30 1
Por valor:    10 20 30

Qué observar, paso a paso:

  1. El array socios empieza como 10, 20, 30, 1, con n igual a 4. La llamada quitarPorPosicion(socios, n, 1) es lo que hace remove(1): la posición 1, sin mirar qué valor hay allí.
  2. Dentro del método, el bucle copia a[2] sobre a[1] y luego a[3] sobre a[2]. Verás que el 20 desaparece, aplastado por el 30, y que el 1 retrocede una posición.
  3. De vuelta en main, n vale 3 y solo se imprimen los tres primeros: 10 30 1. El 1 sigue ahí: el programa quitó a un socio distinto del que querías.
  4. En la segunda parte, buscar(otros, m, 1) primero localiza el valor y devuelve su posición (3). Ahora sí quitarPorPosicion desplaza lo que viene detrás del 1, que es nada. Resultado: 10 20 30. Eso es lo que hace remove(Integer.valueOf(1)): buscar por valor y luego borrar.

Chuletario: Colecciones

Lo esencial en 30 segundos

  • **List**: orden y repetidos; ArrayList por defecto. **Set: sin repetidos. Map: clave → valor. Deque/Queue**: colas y pilas.
  • Declara con la interfaz (List<T> x = new ArrayList<>()), construye con la clase.
  • **HashSet / HashMap**: sin orden, usan hashCode() + equals(). **LinkedHash...: orden de inserción. Tree...**: ordenados (Comparable o Comparator).
  • **equals() y hashCode() siempre juntos** y con los mismos campos (o un record).
  • **Un get que da null**: o la clave no está o preguntas con otro tipo.
  • Para borrar mientras recorres: removeIf o el iterador, nunca el remove de la colección.

Sintaxis

Quiero…CódigoResultado
ListaList<Libro> l = new ArrayList<>();Orden de inserción, admite repetidos
Quitar por posición / por valorl.remove(0); / l.remove(Integer.valueOf(5));Con List<Integer>, ojo al tipo
Conjunto sin orden / ordenadonew HashSet<>() / new TreeSet<>()add devuelve false si ya estaba
Eliminar repetidos de una listanew LinkedHashSet<>(lista)Conserva el orden
MapaMap<String, Libro> m = new HashMap<>();put, get, containsKey
Valor con repuestom.getOrDefault(k, 0)Evita el null
Contar aparicionesm.merge(k, 1, Integer::sum);Suma 1 o empieza en 1
Agrupar en listasm.computeIfAbsent(k, x -> new ArrayList<>()).add(v);Crea la lista solo si falta
Recorrer clave y valorfor (Map.Entry<K, V> e : m.entrySet())e.getKey(), e.getValue()
Borrar por condiciónm.entrySet().removeIf(e -> …);Seguro mientras recorres
Intersección / diferenciaa.retainAll(b); / a.removeAll(b);Sobre una copia, no sobre el original
Cola / pilaDeque<T> d = new ArrayDeque<>();offer+poll / push+pop
Cola por prioridadnew PriorityQueue<>()Sale el menor; exige Comparable

Plantillas de los ejercicios

Recorrer una vez y rellenar dos colecciones (Nivel 1):

Set<String> autores = new TreeSet<>();
Map<String, Libro> porIsbn = new HashMap<>();
for (Libro l : libros) {
    autores.add(l.autor());
    porIsbn.put(l.isbn(), l);
}

Agrupar en listas (Nivel 2):

Map<String, List<String>> porAutor = new TreeMap<>();
for (Libro l : libros) {
    porAutor.computeIfAbsent(l.autor(), k -> new ArrayList<>()).add(l.titulo());
}

Operar con conjuntos sin tocar el original (Nivel 3):

Set<String> comunes = new TreeSet<>(a.keySet());   // copia, no la vista
comunes.retainAll(b.keySet());                      // intersección
Set<String> solo = new TreeSet<>(a.keySet());
solo.removeAll(b.keySet());                         // diferencia

Errores que más se repiten

SíntomaCausa probable
El HashSet tiene «repetidos»Faltan equals() y hashCode(), o solo uno de los dos
Un objeto está en el HashSet pero contains da falseCambiaste un campo que entra en el hash después de guardarlo
get devuelve null aunque la clave «está»Preguntas con otro tipo ("b" en vez de 'b')
NullPointerException al sumar o asignar un getLa clave no existe: usa getOrDefault o merge
ConcurrentModificationExceptionModificas la colección o el mapa por fuera mientras lo recorres
ClassCastException al añadir a un TreeSet/TreeMapLos elementos no son Comparable y no diste un Comparator
remove(1) quita el elemento equivocadoEn List<Integer>, remove(int) borra por posición
Se borra algo del mapa original sin quererModificaste un keySet()/values() (son vistas) sin copiar
El orden sale distinto de lo esperadoUn Hash... no tiene orden: usa LinkedHash... o Tree...

Antes de entregar, pregúntate

  1. ¿He elegido la colección por lo que promete (orden, repetidos, búsqueda por clave)?
  2. Si uso mis objetos como clave o en un Set, ¿tienen **equals() y hashCode()** coherentes (o son un record)?
  3. ¿Mis consultas a un Map o Set usan el mismo tipo que las claves?
  4. ¿Borro mientras recorro con **removeIf o un iterador**, y modifico copias en lugar de vistas?

Si quieres repasar el tema desde la base, vuelve a la teoría de colecciones.

Sigue aprendiendoTodos los artículos de Java

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Sigue aprendiendo

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *