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.
Tabla de Contenidos
Cómo usar este artículo
- Lee el enunciado de un nivel y no mires la solución todavía.
- Escribe tu versión, compílala y compara tu salida con la esperada.
- Si te atascas, mira solo la 🔵 pista y vuelve a intentarlo.
- 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:
| ISBN | Título | Autor |
|---|---|---|
| L-101 | Dune | Herbert |
| L-102 | Emma | Austen |
| L-103 | Hamlet | Shakespeare |
| L-104 | Macbeth | Shakespeare |
| L-105 | Rebecca | du Maurier |
| L-106 | Misery | King |
| L-107 | Carrie | King |
| L-108 | It | King |
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 enapero no enb.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-eachsobre la lista rellena las dos colecciones a la vez: no hace falta recorrer la lista dos veces. autoreses unTreeSet: no guarda repetidos (los tres libros deKingdejan una sola entrada) y se imprime ordenado. Como sonString, el orden es el natural, y por esodu Maurierqueda al final: las minúsculas van detrás de las mayúsculas.porIsbnes unHashMap<String, Libro>: la clave es el ISBN y el valor, el libro entero. Buscar por clave no depende del tamaño del mapa.containsKeyantes degetevita imprimir unnull. También valdríagetOrDefault, pero entonces el mensaje «no existe» tendría que ser unLibroinventado.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 sernew 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í:
TreeMapordena 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
Kingsalen como en la lista original:Misery, Carrie, It. - Para el máximo,
masLibrosempieza ennully 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 porquemasLibroses 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 dea, ordenada por clave. Asífusionarno toca el mapa original.b.forEach((isbn, n) -> ...)recorre cada pareja deb;mergesuma el valor si el ISBN ya estaba en el resultado, o lo crea si no.enComunysoloEnempiezan connew TreeSet<>(a.keySet()): una copia de las claves. Sin esa copia,retainAllyremoveAllactuarían sobre la vista y **borrarían claves dea**. La última línea de la salida (El centro sigue igual) lo comprueba.retainAll(b.keySet())conserva solo lo que está también enb(intersección);removeAll(b.keySet())quita lo que está enb(diferencia).totalrecorrevalues()con unfor-each. Elint nrecibe cadaIntegerpor 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-102pasa a 3 (1+2) yL-103tambié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:
- El array
sociosempieza como10, 20, 30, 1, connigual a 4. La llamadaquitarPorPosicion(socios, n, 1)es lo que haceremove(1): la posición 1, sin mirar qué valor hay allí. - Dentro del método, el bucle copia
a[2]sobrea[1]y luegoa[3]sobrea[2]. Verás que el20desaparece, aplastado por el30, y que el1retrocede una posición. - De vuelta en
main,nvale 3 y solo se imprimen los tres primeros:10 30 1. El1sigue ahí: el programa quitó a un socio distinto del que querías. - En la segunda parte,
buscar(otros, m, 1)primero localiza el valor y devuelve su posición (3). Ahora síquitarPorPosiciondesplaza lo que viene detrás del1, que es nada. Resultado:10 20 30. Eso es lo que haceremove(Integer.valueOf(1)): buscar por valor y luego borrar.
Chuletario: Colecciones
Lo esencial en 30 segundos
- **
List**: orden y repetidos;ArrayListpor 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, usanhashCode()+equals(). **LinkedHash...: orden de inserción.Tree...**: ordenados (ComparableoComparator). - **
equals()yhashCode()siempre juntos** y con los mismos campos (o unrecord). - **Un
getque danull**: o la clave no está o preguntas con otro tipo. - Para borrar mientras recorres:
removeIfo el iterador, nunca elremovede la colección.
Sintaxis
| Quiero… | Código | Resultado |
|---|---|---|
| Lista | List<Libro> l = new ArrayList<>(); | Orden de inserción, admite repetidos |
| Quitar por posición / por valor | l.remove(0); / l.remove(Integer.valueOf(5)); | Con List<Integer>, ojo al tipo |
| Conjunto sin orden / ordenado | new HashSet<>() / new TreeSet<>() | add devuelve false si ya estaba |
| Eliminar repetidos de una lista | new LinkedHashSet<>(lista) | Conserva el orden |
| Mapa | Map<String, Libro> m = new HashMap<>(); | put, get, containsKey |
| Valor con repuesto | m.getOrDefault(k, 0) | Evita el null |
| Contar apariciones | m.merge(k, 1, Integer::sum); | Suma 1 o empieza en 1 |
| Agrupar en listas | m.computeIfAbsent(k, x -> new ArrayList<>()).add(v); | Crea la lista solo si falta |
| Recorrer clave y valor | for (Map.Entry<K, V> e : m.entrySet()) | e.getKey(), e.getValue() |
| Borrar por condición | m.entrySet().removeIf(e -> …); | Seguro mientras recorres |
| Intersección / diferencia | a.retainAll(b); / a.removeAll(b); | Sobre una copia, no sobre el original |
| Cola / pila | Deque<T> d = new ArrayDeque<>(); | offer+poll / push+pop |
| Cola por prioridad | new 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íntoma | Causa probable |
|---|---|
El HashSet tiene «repetidos» | Faltan equals() y hashCode(), o solo uno de los dos |
Un objeto está en el HashSet pero contains da false | Cambiaste 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 get | La clave no existe: usa getOrDefault o merge |
ConcurrentModificationException | Modificas la colección o el mapa por fuera mientras lo recorres |
ClassCastException al añadir a un TreeSet/TreeMap | Los elementos no son Comparable y no diste un Comparator |
remove(1) quita el elemento equivocado | En List<Integer>, remove(int) borra por posición |
| Se borra algo del mapa original sin querer | Modificaste un keySet()/values() (son vistas) sin copiar |
| El orden sale distinto de lo esperado | Un Hash... no tiene orden: usa LinkedHash... o Tree... |
Antes de entregar, pregúntate
- ¿He elegido la colección por lo que promete (orden, repetidos, búsqueda por clave)?
- Si uso mis objetos como clave o en un
Set, ¿tienen **equals()yhashCode()** coherentes (o son unrecord)? - ¿Mis consultas a un
MapoSetusan el mismo tipo que las claves? - ¿Borro mientras recorro con **
removeIfo 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 →
