classDiagram
class Iterable~T~ {
<<interface>>
+iterator() Iterator~T~
}
class Collection~T~ {
<<interface>>
+add(e) boolean
+remove(o) boolean
+size() int
+contains(o) boolean
}
class List~T~ {
<<interface>>
+get(i) T
+set(i, e) T
}
class Set~T~ {
<<interface>>
}
class Map~K,V~ {
<<interface>>
+put(k, v) V
+get(k) V
}
Iterable <|-- Collection
Collection <|-- List
Collection <|-- Set
List <|.. ArrayList
List <|.. LinkedList
Set <|.. HashSet
Set <|.. TreeSet
Map <|.. HashMap
Map <|.. TreeMap
15 Col·leccions i genericitat
Objectius
- Entendre que són les col·leccions de Java.
- Comprendre què és la genericitat (
<T>) i per què fa el codi més segur. - Distingir les tres grans famílies de col·leccions: List, Set i Map.
- Triar entre
ArrayListiLinkedListsegons l’ús. - Recórrer col·leccions amb el for-each i amb iteradors.
- Ordenar objectes amb
Comparable(ordre natural) iComparator(ordres a mida).
Ja hem vist els arrays: contenidors de mida fixa. Són útils però tenen tenen l’inconvenient que has de decidir la mida per endavant i un cop creats, no creixen.
Si tens un array de 10 posicions i arriba l’element número 11, tenim una situació que cal gestionar.
Les col·leccions de Java resolen això: són contenidors que creixen i decreixen sols, amb mètodes per afegir, esborrar, cercar i ordenar.
15.1 El mapa de les col·leccions
Totes les col·leccions comparteixen una arrel comuna, la interfície Collection, amb un contracte mínim (add, remove, size, contains…).
Els mapes (Map) van per lliure: no són Collection, perquè guarden parelles clau-valor.
Quan escrius codi, la bona pràctica és declarar per la interfície i instanciar per la classe:
List<String> tasques = new ArrayList<>(); // Declaro List, instancio ArrayListAixí, si un dia canvies ArrayList per LinkedList, només toques una línia: la resta del codi parla amb List i ni se n’assabenta.
15.2 Genericitat
La genericitat permet dir a una classe quin tipus d’objectes guardarà.
Per exemple, List<Llibre> vol dir “una llista de llibres”. Aquesta class <Llibre> és el paràmetre de tipus.
Sense genericitat, una col·lecció guardaria Object (qualsevol cosa) i, en treure elements, hauries de fer càsting i esperar que fos del tipus esperat. Amb genericitat, el compilador comprova els tipus per tu: si intentes ficar un Soci dins d’una List<Llibre>, no compila.
Exemple:
// Sense genericitat (estil antic, EVITAR): cal càsting i és insegur
List llistaAntiga = new ArrayList();
llistaAntiga.add("Hola");
String s = (String) llistaAntiga.get(0); // Càsting obligatori
// Amb genericitat: segura i sense càsting
List<String> noms = new ArrayList<>(); // <> = "diamant", infereix el tipus
noms.add("Marta");
String primer = noms.get(0); // Ja és String, sense càstingFixa’t en el diamant <> a la dreta: des de Java 7 no cal repetir el tipus, el compilador l’endevina a partir de l’esquerra.
List<Integer> edats = new ArrayList<>();
edats.add(25);
edats.add("trenta"); // ERROR de compilació: "trenta" no és IntegerEl compilador et frena abans d’executar, és a dir, es detecta fàcilment i és ràpid de corregir.
Les col·leccions només guarden objectes, no primitius. No pots fer List<int>. Has d’usar les classes embolcall: List<Integer>, List<Double>, List<Boolean>.
Java converteix int en Integer automàticament (autoboxing). A la pràctica escrius edats.add(25) amb naturalitat.
Declara una llista d’objectes Llibre i afegeix-hi dos llibres. Quin missatge dona el compilador si hi afegeixes un String?
15.3 List
Una List és una seqüència amb índex: cada element té una posició (0, 1, 2…), l’ordre d’inserció es manté i s’admeten duplicats.
És la col·lecció més semblant a un array però dinàmica.
Exemple:
List<String> compra = new ArrayList<>();
compra.add("Pa");
compra.add("Llet");
compra.add("Pa"); // Duplicat permès
System.out.println(compra); // [Pa, Llet, Pa]
System.out.println(compra.get(1)); // Llet (índex 1)
compra.set(1, "Iogurt"); // Substitueix la posició 1
compra.remove("Pa"); // Esborra la PRIMERA aparició de "pa"
System.out.println(compra.size()); // 2ArrayList vs LinkedList
Les dues són una List, però per dins són molt diferents.
ArrayList guarda els elements en un array intern; LinkedList els encadena com una llista d’anells (cada node apunta al següent).
| Operació | ArrayList |
LinkedList |
|---|---|---|
Accés per índex get(i) |
Ràpid (directe) | Lent (cal recórrer) |
| Afegir/treure al final | Ràpid | Ràpid |
| Afegir/treure al principi o al mig | Lent (desplaça elements) | Ràpid (recol·loca enllaços) |
| Memòria | Compacta | Més (guarda enllaços) |
| Ús habitual | El 90 % dels casos | Cues/piles amb molta inserció als extrems |
Comença sempre amb ArrayList. Només canvia a LinkedList si has mesurat que insereixes o esborres molt pels extrems i que això és el teu coll d’ampolla.
En cas de dubte, utilitza: ArrayList.
for (String x : compra) {
if (x.equals("pa")) compra.remove(x); // ConcurrentModificationException!
}No pots afegir ni treure d’una col·lecció mentre la recorres amb un for-each.
La solució correcta és l’ús d’iteradors, que veurem més endavant.
Crea una List<Integer> amb les notes 5, 8, 3, 8. Mostra quantes n’hi ha i quantes vegades apareix el 8 amb Collections.frequency(notes, 8).
15.4 Set
Un Set és un conjunt matemàtic: no admet duplicats. Si afegeixes un element que ja hi és, no passa res (no es duplica). N’hi ha dues variants molt usades:
HashSet: rapidíssim, però sense ordre garantit.TreeSet: manté els elements ordenats (ordre natural o d’unComparator).
Un Set és el registre de sòcies d’un club: cada persona hi consta un sol cop. Tant se val que s’apunti dues vegades: al llistat hi surt una sola.
Exemple:
Set<String> etiquetes = new HashSet<>();
etiquetes.add("java");
etiquetes.add("poo");
etiquetes.add("java"); // Ignorat: ja hi era
System.out.println(etiquetes.size()); // 2
System.out.println(etiquetes.contains("poo")); // true
// TreeSet: sempre ordenat
Set<Integer> ordenat = new TreeSet<>();
ordenat.add(30); ordenat.add(10); ordenat.add(20);
System.out.println(ordenat); // [10, 20, 30]equals/hashCode mal definits
HashSet decideix si dos objectes són “el mateix” amb equals() i hashCode().
Si crees una classe Llibre i no els sobreescrius, dos llibres amb el mateix ISBN es consideraran diferents i s’hi colaran duplicats lògics.
Regla d’or: si un objecte va a un Set o és clau d’un Map, sobreescriu equals i hashCode alhora.
Tens una List<String> amb correus repetits. Com obtindries la llista de correus únics en una sola línia? (Pista: new HashSet<>(llista).)
15.5 Mapa
Un Map associa una clau única a un valor. No té índexs numèrics: accedeixes pel valor a través de la clau. Les claus no es repeteixen; els valors sí.
HashMap: el més usat, sense ordre.TreeMap: ordenat per clau.
Un Map és un diccionari: busques una paraula (clau) i obtens la seva definició (valor).
No busques “la definició número 5”; busques per paraula.
Exemple:
Map<String, Integer> estoc = new HashMap<>();
estoc.put("pomes", 50); // Clau "pomes" → valor 50
estoc.put("peres", 30);
estoc.put("pomes", 42); // MATEIXA clau → sobreescriu (ara 42)
System.out.println(estoc.get("pomes")); // 42
System.out.println(estoc.getOrDefault("kiwis", 0)); // 0 (no hi és)
System.out.println(estoc.containsKey("peres")); // true
// Recórrer un mapa: parelles (entrySet)
for (Map.Entry<String, Integer> e : estoc.entrySet()) {
System.out.println(e.getKey() + " → " + e.getValue());
}Una orientació per quan fer servir cada tipus de col·lecció:
| Necessito… | Col·lecció | Exemple |
|---|---|---|
| Ordre i posicions, admet duplicats | List | Historial de comandes |
| Elements únics, sense parella | Set | Etiquetes d’un article |
| Associar una clau a un valor | Map | DNI → Soci, producte → estoc |
get sobre clau inexistent
Abans d’accedir a un valor per la seva clau, hem d’estar segurs que existeix o controlar-ho:
int n = estoc.get("kiwis"); // NullPointerExceptionSi la clau pot no existir, fes servir getOrDefault(clau, valorPerDefecte) o comprova abans amb containsKey.
Fes un Map<String, Integer> que compti quantes vegades apareix cada paraula d’una frase.
Pista: comptador.merge(paraula, 1, Integer::sum); incrementa o inicialitza en una línia.
15.6 Iteradors
El for-each és la manera còmoda de recórrer qualsevol element Iterable.
Però quan necessites esborrar elements durant el recorregut, cal l’iterador explícit: un objecte que recorre la col·lecció amb:
hasNext(): comprova si en queda algun element
next(): obté el següent element i avançar la posició.
Un iterador és un guia de museu: et va portant de sala en sala.
hasNext()es pregunta si encara queda alguna sala per veure.
next()passa a la següent sala.
Amb remove() pot, a més, tancar la sala per la qual acaba de passar sense trencar el recorregut.
Exemple:
List<Integer> notes = new ArrayList<>(List.of(5, 8, 3, 9, 4));
// For-each: recorregut senzill (només lectura)
int suma = 0;
for (int n : notes) {
suma += n;
}
// Iterador: recorregut amb esborrat segur
Iterator<Integer> it = notes.iterator();
while (it.hasNext()) {
int n = it.next();
if (n < 5) {
it.remove(); // Esborra el suspès de forma SEGURA
}
}
System.out.println(notes); // [5, 8, 9]flowchart LR
A["hasNext()?"] -->|true| B["next()"]
B --> C{"condició?"}
C -->|sí| D["remove()"]
C -->|no| A
D --> A
A -->|false| E["fi del recorregut"]
- For-each: lectura simple, el codi més net.
- Iterador: quan cal esborrar mentre recorres.
- Streams: quan vols transformar o filtrar de forma declarativa. Es veurà més endavant.
Recorre un Map<String, Integer> amb un iterador sobre entrySet() i esborra totes les entrades amb valor 0.
15.7 Ordenar objectes
Ordenar Integer o String és fàcil: ja saben comparar-se.
Però, com s’ordenen objectes Llibre? Per títol? Per any? Per pàgines?
Java ofereix dues eines:
Comparable— la classe defineix el seu ordre natural implementantcompareTo. N’hi ha un de sol.Comparator— un objecte extern que defineix un ordre concret. En pots tenir molts (per títol, per any…).
El mètode compareTo/compare retorna:
- Un negatiu si “aquest va abans”.
- Un zero si són iguals.
- Un positiu si “aquest va després”.
Comparable és l’ordre de fàbrica d’un producte (com ve ordenat el catàleg).
Comparator és ordenar la teva prestatgeria com tu vulguis: per color, per mida, per data… Cada Comparator és un criteri diferent aplicat des de fora.
Exemple amb Comparable:
public class Llibre implements Comparable<Llibre> {
private String titol;
private int any;
public Llibre(String titol, int any) {
this.titol = titol;
this.any = any;
}
public String getTitol() { return titol; }
public int getAny() { return any; }
// Ordre natural: alfabètic per títol
@Override
public int compareTo(Llibre altre) {
return this.titol.compareTo(altre.titol);
}
@Override
public String toString() { return titol + " (" + any + ")"; }
}I ho podem utilitzar:
List<Llibre> biblioteca = new ArrayList<>(List.of(
new Llibre("Java a fons", 2021),
new Llibre("Algorísmica", 2019),
new Llibre("POO pràctica", 2023)
));
Collections.sort(biblioteca); // Usa compareTo → per títol
System.out.println(biblioteca);Exemple amb Comparator:
// Per any, de més antic a més nou
biblioteca.sort(Comparator.comparingInt(Llibre::getAny));
// Per títol, descendent
biblioteca.sort(Comparator.comparing(Llibre::getTitol).reversed());
// Per any i, en cas d'empat, per títol
biblioteca.sort(
Comparator.comparingInt(Llibre::getAny)
.thenComparing(Llibre::getTitol)
);Els Comparator es construeixen de forma declarativa amb comparing, comparingInt, reversed i thenComparing.
Aquestes referències a mètode (Llibre::getAny) es veuran més endavant.
compareTo incoherent amb equals
Si compareTo retorna 0 per a dos objectes que equals considera diferents (o a l’inrevés), un TreeSet o un TreeMap es comportaran de forma estranya.
Mantén els dos criteris coherents: si dos objectes són iguals per equals, compareTo hauria de tornar 0.
Collections: la navalla suïssa
La classe utilitària Collections porta mètodes molt útils: sort, reverse, shuffle, max, min, frequency, unmodifiableList… Val la pena fer-hi una ullada.
Afegeix a Llibre un camp pagines. Ordena la biblioteca per nombre de pàgines descendent i, en cas d’empat, alfabèticament per títol.
Fes-ho amb un sol Comparator encadenat.
15.8 Resum
- Les col·leccions són contenidors dinàmics: creixen i decreixen sols, a diferència dels arrays.
- List és una seqüència amb índex i duplicats (
ArrayListper defecte;LinkedListper a molta inserció als extrems). - Set conté només elements únics (
HashSetsense ordre;TreeSetordenat). Exigeix bonsequals/hashCode. - Map emmagatzema parelles de clau/valor (
HashMap;TreeMapordenat per clau). S’accedeix per clau.
- List és una seqüència amb índex i duplicats (
- La genericitat (
<T>) etiqueta la col·lecció amb el tipus que guarda; el compilador comprova els tipus i evita càstings. - Els iteradors permeten esborrar amb seguretat durant el recorregut.
Comparabledefineix l’ordre natural (un);Comparatordefineix ordres a mida (molts).- Declara pel tipus interfície, instancia per la classe concreta.
15.9 Per practicar
- Agenda telefònica. Crea un
Map<String, String>(nom → telèfon) amb operacions afegir, consultar, esborrar i llistar ordenat per nom.- Compara
HashMapambTreeMap: quina diferència veus en llistar?
- Compara
- Sense repetits. Donada una
List<String>amb noms repetits, obtén la llista de noms únics conservant l’ordre d’aparició. (Pista:LinkedHashSet.) - Rànquing de jugadors. Modela una classe
Jugador(nom, punts). Ordena una llista per punts descendent amb unComparatori mostra el podi (3 primers). - Comptador de paraules. Llegeix una frase, compta quantes vegades apareix cada paraula amb un
Map<String, Integer>i mostra el resultat ordenat per freqüència. - Ampliació del projecte BiblioTech. Guarda els llibres en una
List<Llibre>i els socis en unMap<String, Soci>amb el DNI com a clau. Implementa cerca de llibre per títol i alta/baixa de socis.