Framework
- Les tres peces
- Les interfícies
- Ordre de recorregut garantit
- Escollir la implementació
- Tipus parametritzats
- Referències
Volem guardar els noms dels alumnes d’un grup. Amb un array, la mida queda fixada en crear-lo, i afegir un nom més obliga a copiar-ho tot en un array més gran:
String[] noms = new String[30];
noms[0] = "Aina";
Amb una col·lecció, la mida s’ajusta sola:
List<String> noms = new ArrayList<>();
noms.add("Aina");
noms.add("Bernat");
I si el que necessitem és que no hi hagi noms repetits, canviem el tipus i prou. El codi que omple la col·lecció no es toca:
Set<String> noms = new HashSet<>();
noms.add("Aina");
noms.add("Aina"); // no s'afegeix: la mida continua sent 1
Aquesta és la idea del framework de col·leccions (collections framework): un conjunt de tipus de la biblioteca estàndard per emmagatzemar i manipular grups d’objectes, on triar la garantia que necessitem (ordre, repeticions, accés per posició) és triar un tipus.
Les tres peces
El framework està format per tres coses:
- Interfícies: diuen què garanteix una col·lecció, sense dir com ho aconsegueix.
Listpromet ordre per posició;Setpromet que no hi ha elements repetits. - Implementacions: les estructures de dades concretes que compleixen la promesa.
ArrayListiLinkedListsón dues maneres diferents de ser unaList. - Algorismes: mètodes reutilitzables com ordenar o cercar, agrupats a la classe
Collections. Són polimòrfics: el mateixCollections.sort()serveix per a qualsevol implementació deList, perquè està escrit contra la interfície i no contra una classe concreta.
D’aquí en surt una regla pràctica: declara la variable amb la interfície i tria la classe només en construir-la.
List<String> noms = new ArrayList<>();
Si més endavant una LinkedList resulta més adequada, canviem una paraula i la resta del programa no se n’assabenta.
Les interfícies
Aquestes són les interfícies que faràs servir per declarar variables:
| Interfície | Què garanteix | Exemple d’ús |
|---|---|---|
Collection | Un grup d’elements que es pot recórrer i comptar. És el denominador comú de totes | Un paràmetre que accepta qualsevol col·lecció |
List | Ordre per posició, amb accés per índex; admet repeticions | Les línies d’un fitxer, en ordre |
Set | Cap element repetit, segons equals() | Els DNI que ja hem processat |
SortedSet | A més, recorregut per l’ordre natural dels elements | Una classificació sense empats |
Queue | Elements que esperen a ser processats, habitualment en ordre FIFO | Les tasques pendents d’un servidor |
Deque | Es pot afegir i treure pels dos extrems | Una pila (LIFO) o una cua |
Map | Parelles clau-valor, amb claus úniques | El telèfon de cada persona |
SortedMap | A més, recorregut per l’ordre de les claus | Esdeveniments indexats per data |
Map no és una Collection i per això penja a part de la jerarquia: no conté elements, sinó parelles.
Java no proporciona cap implementació directa de Collection. És un tipus per parlar de col·leccions en general, sobretot als paràmetres dels mètodes: si el teu mètode només recorre els elements, demana una Collection i acceptarà llistes, conjunts i cues indistintament.
Ordre de recorregut garantit
Algunes col·leccions tenen un ordre de recorregut definit i d’altres no. Un HashSet no en té cap: el recorregut pot canviar entre execucions. Una List sí, i un LinkedHashSet també, perquè conserva l’ordre d’inserció.
Java 21 va agrupar les que en tenen sota tres interfícies seqüenciades: SequencedCollection, SequencedSet i SequencedMap. Aporten una manera uniforme d’accedir al primer i a l’últim element, i de recórrer la col·lecció al revés:
List<String> noms = new ArrayList<>(List.of("Aina", "Bernat", "Carla"));
noms.getFirst(); // "Aina"
noms.getLast(); // "Carla"
noms.reversed(); // vista invertida: [Carla, Bernat, Aina]
Abans calia recordar una manera diferent per a cada tipus (list.get(0), deque.peekFirst(), sortedSet.first()). Ara getFirst() funciona a totes.
Escollir la implementació
Per a cada interfície hi ha una implementació que és la tria per defecte, i alternatives que valen la pena en casos concrets:
| Interfície | Per defecte | Alternativa i quan |
|---|---|---|
List | ArrayList | LinkedList si inserim o esborrem sovint pel principi |
Set | HashSet | LinkedHashSet per conservar l’ordre d’inserció, TreeSet per recórrer ordenat |
Queue, Deque | ArrayDeque | PriorityQueue si la sortida ha de seguir una prioritat |
Map | HashMap | LinkedHashMap per l’ordre d’inserció, TreeMap per l’ordre de clau |
Les implementacions Hash necessiten que els elements, o les claus, tinguin ben implementats equals() i hashCode(). Les implementacions Tree necessiten poder comparar els elements. Tots dos requisits es tracten a Comparació d’objectes.
Tipus parametritzats
A la documentació les interfícies apareixen amb un paràmetre entre angles. Per exemple, List<E>. Vol dir que una List no és una llista d’objectes qualssevol: és una llista d’elements d’un tipus que decidim en declarar-la, i el compilador ens avisa si hi posem res més.
List<String> noms = new ArrayList<>();
noms.add(42); // error de compilació
Els tipus primitius no es poden fer servir com a paràmetre de tipus. Cal la classe embolcall (wrapper) corresponent: Integer per a int, Double per a double, Boolean per a boolean. La conversió és automàtica en els dos sentits, i se’n diu autoboxing:
List<Integer> edats = new ArrayList<>();
edats.add(17); // int 17 passa a ser Integer
int primera = edats.get(0); // i a la inversa
Aquest mecanisme, els genèrics, es tracta a fons a Genèrics.