Vas tancar el mòdul 4 amb una promesa i una confessió. La promesa: els arrays provisionals de BiblioTech es convertiran en col·leccions. La confessió: cada vegada que el projecte necessita guardar moltes coses, recorre a un Material[] cataleg = new Material[10] que cal copiar sencer per afegir-hi un element i recórrer complet per trobar-ne un. Abans de substituir-los convé entendre'ls de debò, perquè l'array no desapareix: ArrayList és un array per dins, HashMap és un array de cubells, ArrayDeque és un array circular, i String és un array de bytes. Tot el Framework de Col·leccions que veuràs en les vuit lliçons següents està construït sobre arrays.

Un array (el terme que farem servir gairebé sempre, perquè és el que veuràs en tot el codi real) és l'estructura de dades més primitiva i més ràpida de Java: un bloc contigu de memòria, de mida fixa, que guarda elements del mateix tipus i permet accedir a qualsevol d'ells per la seva posició en temps constant. Aquestes quatre paraules —contigu, fix, homogeni, constant— expliquen alhora tota la seva potència i totes les seves limitacions. En aquesta lliçó les veuràs en detall, aprendràs per fi el bucle for-each que va quedar pendent al mòdul 2, dominaràs la classe d'utilitats Arrays i acabaràs amb una llista clara de les coses que un array no pot fer: exactament la llista de motius pels quals existeix el Framework de Col·leccions.

Contingut

  1. Què és realment un array
  2. Declarar, crear i inicialitzar
  3. Valors per defecte
  4. Índexs, length i els límits de l'array
  5. Recorregut amb for clàssic
  6. El bucle for-each
  7. Arrays de primitius enfront d'arrays d'objectes
  8. Arrays multidimensionals i dentats
  9. La classe Arrays
  10. Copiar arrays
  11. Varargs per dins és un array
  12. Aplicació: el catàleg de BiblioTech
  13. Les limitacions que motiven les col·leccions
  14. Errors Habituals i Consells
  15. Exercicis

  1. Què és realment un array

Un array és un bloc contigu de memòria reservat d'una sola vegada, dividit en cel·les de la mateixa mida. Quan escrius:

int[] diesRetard = new int[5];

la JVM reserva al heap espai suficient per a cinc enters (cinc vegades quatre bytes) més una petita capçalera amb el tipus i la longitud. Totes les cel·les estan enganxades una darrere l'altra:

flowchart LR
    subgraph heap["Objecte array al heap"]
        direction LR
        H["capcalera<br/>tipus: int[]<br/>length: 5"]
        C0["[0]<br/>0"]
        C1["[1]<br/>0"]
        C2["[2]<br/>0"]
        C3["[3]<br/>0"]
        C4["[4]<br/>0"]
    end
    ref["diesRetard<br/>(referencia a la pila)"] --> H

Aquesta contigüitat és la clau de tot. Per llegir diesRetard[3], la JVM no busca res: calcula adreca_base + 3 * 4 bytes i llegeix. Una multiplicació i una suma, sempre la mateixa feina, tant li fa que l'array tingui cinc elements o cinc milions. A això en diem accés en temps constant, o O(1).

Notació O(): com llegir-la. Al llarg d'aquest mòdul compararàs estructures de dades pel seu cost. La notació O() descriu com creix el temps d'una operació quan creix el nombre d'elements n, ignorant constants i detalls de màquina. O(1) significa temps constant: tant se val que hi hagi 10 o 10 milions d'elements, costa el mateix. O(n) significa lineal: amb el doble d'elements, el doble de temps (recórrer un array sencer). O(log n) significa que doblar els elements només afegeix un pas més (cerca binària: amb un milió d'elements, una vintena de passos). O(n²) significa que doblar els elements quadruplica el temps (dos bucles imbricats; és el que fa avui l'informePerTipus de BiblioTech). No és una mesura de segons, és una mesura de com escala.

I hi ha un segon avantatge menys obvi: la localitat de memòria cau. Els processadors moderns no llegeixen la memòria byte a byte, sinó en blocs (línies de memòria cau de 64 bytes típicament). En llegir diesRetard[0], el processador porta també els elements següents "de franc". Per això recórrer un array és dramàticament més ràpid que recórrer una estructura amb els elements dispersos per la memòria: ho veuràs amb números a 05-04, quan comparis ArrayList amb LinkedList.

Les tres propietats que defineixen un array a Java:

Propietat Què significa Conseqüència
Mida fixa Es decideix en crear-lo i no es pot canviar Per "afegir" cal crear un altre array i copiar
Tipus homogeni Tots els elements són del mateix tipus declarat El compilador garanteix què hi ha a dins
És un objecte Viu al heap; la variable guarda una referència Es passa per referència, admet null, hereta d'Object

Aquest tercer punt sorprèn molta gent: un array és un objecte, encara que no existeixi cap classe Array que puguis veure. int[], String[] i Material[] són tipus reals que hereten d'Object, de manera que un array té hashCode(), toString() i getClass(). Això tindrà conseqüències importants a l'apartat 9.

  1. Declarar, crear i inicialitzar

Són tres operacions diferents i convé no confondre-les.

Declarar només crea la variable que guardarà la referència. Encara no hi ha cap array:

Material[] cataleg;         // forma recomanada: els claudators al costat del TIPUS
Material cataleg2[];        // legal, herencia de C, DESACONSELLADA

Fes servir sempre la primera forma. La segona compila, però amaga informació: a Material cataleg[], altre;, cataleg és un array i altre no ho és, cosa que és una font clàssica de confusió.

Crear reserva la memòria amb new, i aquí és on es fixa la mida per sempre:

Material[] cataleg  = new Material[10];      // 10 celles, totes a null
int[] diesRetard    = new int[5];            // 5 celles, totes a 0

La mida pot ser una variable calculada en execució, cosa que sí que és flexible:

int quants = Integer.parseInt(entrada.nextLine());
Material[] seleccio = new Material[quants];

El que no pot fer és canviar després. seleccio mesurarà quants per sempre.

Inicialitzar amb literal crea i omple en una sola expressió, i la mida es dedueix dels elements:

String[] empleats = { "Marta Ruiz", "Diego Alonso", "Nuria Vidal" };   // length = 3
double[] tarifes  = { 0.25, 0.10, 0.50 };

Aquesta forma abreujada només val a la declaració. Si vols assignar un array literal a una variable ja declarada, o passar-lo directament com a argument, necessites la forma completa amb new:

String[] empleats;
// empleats = { "Marta Ruiz" };                    // ERROR de compilacio
empleats = new String[] { "Marta Ruiz" };          // correcte

registrar(new String[] { "Marta Ruiz", "Diego Alonso" });   // correcte com a argument

  1. Valors per defecte

En crear un array amb new, Java omple totes les cel·les amb el valor per defecte del tipus. Això no és un detall menor: significa que un array acabat de crear mai no conté brossa, a diferència del que passa en C.

Tipus de l'element Valor per defecte
byte, short, int, long 0
float, double 0.0
char '\u0000' (caràcter nul, s'imprimeix com a espai en blanc o quadrat)
boolean false
Qualsevol tipus referència (String, Material, Integer...) null

La conseqüència pràctica més important és l'última fila. Un new Material[10] no conté deu materials: conté deu referències null. Continua sent feina teva omplir-les:

Material[] cataleg = new Material[3];
System.out.println(cataleg[0]);              // null
// cataleg[0].getTitol();                    // NullPointerException

cataleg[0] = new Llibre("Java Eficac", "Joshua Bloch", "978-0000000001", 2018);
System.out.println(cataleg[0].getTitol());   // Java Eficac

I també explica el parany clàssic de comptar elements: si crees new Material[10] i només n'omples tres, cataleg.length val 10, no 3. L'array no sap quantes cel·les has fet servir; per això tot el BiblioTech del mòdul 4 arrossega variables auxiliars com int n = 0 per portar el compte a mà. Les col·leccions eliminen exactament aquest problema.

  1. Índexs, length i els límits de l'array

Els índexs d'un array van de 0 a length - 1. Que comencin a 0 no és cap caprici: l'índex és literalment el desplaçament des de l'inici del bloc. El primer element és a distància zero de la base.

String[] empleats = { "Marta Ruiz", "Diego Alonso", "Nuria Vidal" };

System.out.println(empleats.length);        // 3
System.out.println(empleats[0]);            // Marta Ruiz     (el primer)
System.out.println(empleats[2]);            // Nuria Vidal    (el ultim)
System.out.println(empleats[empleats.length - 1]);   // Nuria Vidal, idioma estandard

length és un atribut, no un mètode. S'escriu array.length, sense parèntesis. És una de les incoherències històriques de Java que més confusió genera, perquè per a String és un mètode (text.length()) i per a les col·leccions és un altre mètode diferent (llista.size()). Memoritza els tres:

Tipus Com s'obté la mida
Array array.length — atribut, sense parèntesis
String text.length() — mètode
Col·lecció (List, Set, Map...) colleccio.size() — mètode

Si accedeixes fora de rang, la JVM ho detecta i llança ArrayIndexOutOfBoundsException:

System.out.println(empleats[3]);      // ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
System.out.println(empleats[-1]);     // ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 3

Això és una virtut, no un defecte: Java comprova cada accés i atura el programa al punt exacte de l'error, en lloc de llegir memòria aliena silenciosament com faria C. El cost d'aquesta comprovació és mínim i el compilador JIT l'elimina quan pot demostrar que l'índex és segur. Com capturar i tractar aquesta excepció és el tema del mòdul 6; de moment, evita-la comprovant els límits abans d'accedir-hi.

  1. Recorregut amb for clàssic

El bucle for que coneixes des de 02-02 és la forma completa de recórrer un array, i continua sent la correcta quan necessites l'índex:

double[] multes = { 3.75, 0.0, 12.50, 1.25 };

for (int i = 0; i < multes.length; i++) {
    System.out.printf("Prestec %d -> multa %.2f EUR%n", i + 1, multes[i]);
}

Tres detalls que eviten la majoria dels errors de recorregut:

  • i < multes.length, mai i <= multes.length. L'últim índex vàlid és length - 1.
  • Fes servir multes.length, no una constant literal com ara 4. Si demà l'array creix, el bucle s'hi adapta tot sol.
  • Comença a 0, llevat que tinguis un motiu explícit.

El for clàssic és imprescindible quan el recorregut necessita alguna cosa més que els valors:

// Recorregut cap enrere
for (int i = multes.length - 1; i >= 0; i--) { ... }

// Recorregut de dos en dos
for (int i = 0; i < multes.length; i += 2) { ... }

// Comparar cada element amb el seguent (compte amb el limit)
for (int i = 0; i < multes.length - 1; i++) {
    if (multes[i] > multes[i + 1]) { ... }
}

// MODIFICAR el contingut de l'array
for (int i = 0; i < multes.length; i++) {
    multes[i] = Math.min(multes[i], 20.0);      // aplicar el sostre de multa
}

Aquest últim és el més important de recordar per a l'apartat següent.

  1. El bucle for-each

Al mòdul 2 es va ajornar el for-each perquè no tenia sentit explicar-lo sense res a recórrer. Ja ho tens.

El bucle for millorat o for-each (Java 5) recorre tots els elements d'un array o de qualsevol objecte Iterable —cosa que inclou totes les col·leccions que veuràs en aquest mòdul— sense gestionar índexs:

for (Tipus element : colleccioOArray) {
    // usar 'element'
}

Es llegeix literalment "per a cada element de tipus Tipus a colleccioOArray". Els dos punts es pronuncien "a".

Compara les dues versions del mateix recorregut:

String[] empleats = { "Marta Ruiz", "Diego Alonso", "Nuria Vidal" };

// for classic: 3 elements que poden fallar (inici, condicio, increment)
for (int i = 0; i < empleats.length; i++) {
    System.out.println("Empleat: " + empleats[i]);
}

// for-each: sense index, sense condicio, sense increment
for (String nom : empleats) {
    System.out.println("Empleat: " + nom);
}

I amb objectes del projecte, on brilla de debò:

Material[] cataleg = {
    new Llibre("Java Eficac",        "Joshua Bloch",  "978-0000000001", 2018),
    new Llibre("Patrons de Disseny", "Erich Gamma",   "978-0000000002", 1994),
    new Revista("Java Magazine",     "REV-2024-03",   42, "Mensual"),
    new Dvd("Refactoritzacio en directe", "DVD-0007", 95)
};

for (Material m : cataleg) {
    System.out.println(m.descriure());     // polimorfisme de 03-06, sense ni un sol index
}

Avantatges

  • Menys codi i menys errors. Desapareixen les tres fonts clàssiques de fallada: començar a 1, acabar amb <=, oblidar l'i++ (bucle infinit).
  • Impossible sortir-se de rang. No hi ha cap índex que puguis equivocar: ArrayIndexOutOfBoundsException no pot passar.
  • Expressa la intenció. "Recorro tots els elements, un per un, cap endavant." Qui llegeix el teu codi ho sap a l'instant.
  • Funciona igual amb arrays i amb col·leccions. El mateix bucle serveix per a Material[] i per a List<Material>, cosa que farà indolora la refactorització de l'apartat 12.

Limitacions

I ara les tres coses que no pot fer, que són la raó que el for clàssic continuï existint:

1. No et dona l'índex. Si necessites numerar, comparar amb el veí o escriure a la posició i, necessites el for clàssic (o portar un comptador a part, que és exactament el que el for-each intentava evitar).

int posicio = 1;
for (Material m : cataleg) {
    System.out.println(posicio + ". " + m.getTitol());
    posicio++;                                   // funciona, pero delata que volies un for classic
}

2. No permet modificar la cel·la de l'array. Aquesta és la limitació que més es malinterpreta, així que val la pena veure-la amb precisió:

int[] nombres = { 1, 2, 3 };

for (int n : nombres) {
    n = n * 10;                    // NO fa res a l'array
}
System.out.println(Arrays.toString(nombres));    // [1, 2, 3]

La variable n és una còpia del valor de la cel·la (pas per valor, 03-03). Reassignar-la canvia la còpia, no l'array. El mateix passa amb objectes:

for (Material m : cataleg) {
    m = new Llibre("Altre", "Ningu", "REF-X", 2020);   // NO canvia cataleg[i]
}

m és una còpia de la referència. Reassignar-la apunta la còpia a un altre objecte; la cel·la de l'array continua apuntant a l'original.

Però sí que pots modificar l'objecte al qual apunta, perquè aquí no reassignes res, invoques mètodes sobre el mateix objecte:

for (Material m : cataleg) {
    m.prestar();                 // SI que afecta l'objecte real del cataleg
}

La regla, en una línia: el for-each no pot canviar a què apunta cada cel·la, però sí que pot canviar l'estat de l'objecte apuntat.

3. Només va cap endavant i d'un en un. No hi ha recorregut invers, ni salts, ni recorreguts parcials.

Quan fer servir cadascun

Situació Bucle recomanat
Llegir tots els elements, en ordre for-each
Necessites l'índex (numerar, posició, comparar veïns) for clàssic
Escriure a les cel·les (array[i] = ...) for clàssic
Recórrer cap enrere o amb salts for clàssic
Recórrer només una part for clàssic
Recórrer una col·lecció i eliminar mentre recorres Ni l'un ni l'altre: Iterator o removeIf (05-02)

Fes servir for-each per defecte i baixa al for clàssic només quan necessitis el que el for-each no dona. A 05-02 veuràs que el for-each no és cap construcció màgica: el compilador el tradueix a un for clàssic quan recorre un array, i a un Iterator quan recorre una col·lecció.

  1. Arrays de primitius enfront d'arrays d'objectes

Aquí hi ha la diferència estructural que explica la meitat de les sorpreses amb arrays: què guarda realment cada cel·la.

En un array de primitius, la cel·la conté el valor:

int[] anys = { 2018, 1994, 1999 };

En un array d'objectes, la cel·la conté una referència a l'objecte, que viu en un altre lloc del heap:

Material[] cataleg = new Material[3];
cataleg[0] = new Llibre("Java Eficac", "Joshua Bloch", "978-0000000001", 2018);
cataleg[1] = new Revista("Java Magazine", "REV-2024-03", 42, "Mensual");
// cataleg[2] continua a null
flowchart LR
    subgraph prim["int[] anys — els valors son A DINS"]
        direction LR
        P0["[0] 2018"]
        P1["[1] 1994"]
        P2["[2] 1999"]
    end

    subgraph obj["Material[] cataleg — referencies"]
        direction LR
        O0["[0] ref"]
        O1["[1] ref"]
        O2["[2] null"]
    end

    L["Llibre<br/>Java Eficac<br/>978-0000000001"]
    R["Revista<br/>Java Magazine<br/>REV-2024-03"]

    O0 --> L
    O1 --> R

D'aquest diagrama se'n deriven quatre conseqüències que convé tenir molt clares:

  1. Un array d'objectes és més petit del que sembla però costa més de recórrer. Les cel·les només guarden referències (4 o 8 bytes), però seguir cada referència implica un salt a una altra zona del heap, cosa que trenca la localitat de memòria cau de l'apartat 1.
  2. Dues cel·les poden apuntar al mateix objecte (aliasing, 03-02). cataleg[2] = cataleg[0]; no copia el llibre: crea un segon camí cap a ell. Modificar-lo per una via es veu per l'altra.
  3. null és un valor legítim a les cel·les, i cada accés a cataleg[i].alguna() pot llançar NullPointerException si aquella cel·la no es va omplir mai.
  4. L'array és covariant, i això té un parany. Material[] accepta qualsevol subclasse de Material (Llibre, Revista, Dvd), cosa que és còmoda i és el que permet el catàleg polimòrfic. Però també permet això:
Object[] coses = new Llibre[2];              // COMPILA: Llibre[] es un Object[]
coses[0] = new Revista("Java Magazine", "REV-1", 1, "Mensual");   // ArrayStoreException en EXECUCIO

El compilador ho accepta perquè Llibre[] és subtipus d'Object[], però la JVM comprova el tipus real a cada escriptura i llança ArrayStoreException. És un forat de seguretat de tipus que Java 1.0 va acceptar per pragmatisme i que els genèrics van corregir: List<Llibre> no és un List<Object>, precisament perquè aquest error es detecti en compilar. La teoria completa (invariància, comodins, esborrat de tipus) és la lliçó 10-01.

  1. Arrays multidimensionals i dentats

A Java no existeixen els arrays bidimensionals de debò: el que existeix són arrays els elements dels quals són, al seu torn, arrays. Entendre això resol tots els dubtes de cop.

int[][] prestecsPerMes = new int[3][12];   // 3 tipus de material x 12 mesos

Això crea un array de 3 elements, cadascun dels quals és una referència a un array de 12 enters:

flowchart LR
    M["prestecsPerMes<br/>int[][] length=3"]
    F0["[0] → int[12]"]
    F1["[1] → int[12]"]
    F2["[2] → int[12]"]
    M --> F0
    M --> F1
    M --> F2
    F0 --> A0["0 0 0 0 0 0 0 0 0 0 0 0"]
    F1 --> A1["0 0 0 0 0 0 0 0 0 0 0 0"]
    F2 --> A2["0 0 0 0 0 0 0 0 0 0 0 0"]

L'accés porta dos índexs, en ordre fila-columna:

prestecsPerMes[0][3] = 12;                  // tipus 0 (llibres), mes 3 (abril)
System.out.println(prestecsPerMes.length);         // 3   -> nombre de files
System.out.println(prestecsPerMes[0].length);      // 12  -> longitud de la fila 0

El recorregut natural són dos bucles imbricats, un per dimensió:

String[] tipus = { "Llibres", "Revistes", "DVDs" };

for (int t = 0; t < prestecsPerMes.length; t++) {
    int total = 0;
    for (int mes = 0; mes < prestecsPerMes[t].length; mes++) {
        total += prestecsPerMes[t][mes];
    }
    System.out.printf("%-10s %3d prestecs a l'any%n", tipus[t], total);
}

O, si no necessites els índexs, amb for-each imbricat:

for (int[] fila : prestecsPerMes) {
    int total = 0;
    for (int valor : fila) { total += valor; }
    System.out.println("Total de la fila: " + total);
}

Fixa't en el tipus de la variable del bucle extern: int[] fila, no int. Cada element d'un int[][] és un int[].

També hi ha inicialització literal, i aquí es veu clarament que són arrays d'arrays:

int[][] matriu = {
    { 12,  8, 15 },
    {  3,  7,  2 },
    { 20, 11,  9 }
};

Arrays dentats (jagged)

Com que cada fila és un array independent, les files poden tenir longituds diferents. A això se'n diu array dentat o jagged:

String[][] prestecsPerEmpleat = new String[3][];      // 3 files, sense columnes encara

prestecsPerEmpleat[0] = new String[] { "Java Eficac", "Refactoritzacio" };  // Marta: 2
prestecsPerEmpleat[1] = new String[] { "Patrons de Disseny" };              // Diego: 1
prestecsPerEmpleat[2] = new String[0];                                      // Nuria: 0

String[] noms = { "Marta Ruiz", "Diego Alonso", "Nuria Vidal" };

for (int i = 0; i < prestecsPerEmpleat.length; i++) {
    System.out.print(noms[i] + ": ");
    if (prestecsPerEmpleat[i].length == 0) {
        System.out.println("(sense prestecs)");
    } else {
        System.out.println(String.join(", ", prestecsPerEmpleat[i]));
    }
}
Marta Ruiz: Java Eficac, Refactoritzacio
Diego Alonso: Patrons de Disseny
Nuria Vidal: (sense prestecs)

Observa la sintaxi new String[3][]: s'indica el nombre de files i es deixa la segona dimensió buida, perquè cada fila es crearà després amb la seva mida pròpia. Si deixes les files sense crear, valen null, i accedir a prestecsPerEmpleat[0][0] donaria NullPointerException.

Aquesta estructura —"per a cada empleat, una llista de títols de longitud variable"— és just la que a 05-05 substituiràs per un Map<Empleat, List<Prestec>>, molt més expressiu i sense índexs numèrics que calgui mantenir sincronitzats amb un altre array de noms.

  1. La classe Arrays

java.util.Arrays és una classe d'utilitats amb mètodes estàtics que resolen gairebé tot el que necessites fer amb arrays. És de les primeres coses que cal importar en qualsevol programa seriós:

import java.util.Arrays;

toString i deepToString

Un array és un objecte que no sobreescriu toString(), així que imprimir-lo directament mostra la representació d'Object de 03-09 (nom del tipus, arrova, hash en hexadecimal):

int[] anys = { 2018, 1994, 1999 };
System.out.println(anys);                       // [I@1b6d3586    <- inutil
System.out.println(Arrays.toString(anys));      // [2018, 1994, 1999]

Per a arrays de més d'una dimensió, toString no basta —imprimiria la referència de cada fila—, i cal deepToString, que baixa recursivament:

int[][] matriu = { { 1, 2 }, { 3, 4 } };
System.out.println(Arrays.toString(matriu));       // [[I@4554617c, [I@74a14482]
System.out.println(Arrays.deepToString(matriu));   // [[1, 2], [3, 4]]

Arrays.toString és la teva eina número u per depurar arrays. Fes-la servir a cada traça.

sort i sort amb Comparator

Arrays.sort ordena l'array al lloc (in place): modifica l'array rebut i no retorna res.

double[] multes = { 12.50, 0.0, 3.75, 1.25 };
Arrays.sort(multes);
System.out.println(Arrays.toString(multes));     // [0.0, 1.25, 3.75, 12.5]

Amb objectes, hi ha dues variants. Sense Comparator, exigeix que els elements implementin Comparable —cosa que fa Fitxa, tal com vas veure a 04-07— i fa servir el seu ordre natural:

Fitxa[] fitxes = {
    new Fitxa("Refactoritzacio",    "Martin Fowler", 1999),
    new Fitxa("Java Eficac",        "Joshua Bloch",  2018),
    new Fitxa("Patrons de Disseny", "Erich Gamma",   1994)
};
Arrays.sort(fitxes);                             // usa el compareTo de Fitxa (per titol)

I amb un Comparator, que és on es connecta tot el mòdul 4:

Arrays.sort(fitxes, Comparator.comparingInt(Fitxa::any));                     // per any
Arrays.sort(fitxes, Comparator.comparing(Fitxa::autor).thenComparing(Fitxa::titol));
Arrays.sort(fitxes, Comparator.comparingInt(Fitxa::any).reversed());          // mes nous primer

Hi ha a més una variant que ordena només un rang, útil quan l'array té cel·les sense fer servir al final:

Arrays.sort(cataleg, 0, n, Comparator.comparing(Material::getTitol));   // nomes les n primeres

La teoria completa —el contracte de compareTo, l'estabilitat, quin algorisme fa servir Java— és la lliçó de tancament d'aquest mòdul, 05-09.

binarySearch

Busca un element amb cerca binària: mira l'element central, descarta mitja meitat i repeteix. Cost O(log n): en un array d'un milió d'elements, una vintena de comprovacions en lloc d'un milió.

int[] referencies = { 101, 205, 307, 412, 588 };     // JA ORDENAT
System.out.println(Arrays.binarySearch(referencies, 307));    // 2  -> index on es
System.out.println(Arrays.binarySearch(referencies, 400));    // -4 -> no hi es

Dos advertiments essencials:

  • L'array ha d'estar ordenat prèviament, amb el mateix criteri amb què hi busques. Sobre un array desordenat el resultat és brossa, i no hi ha cap avís.
  • El valor negatiu no és "-1 i ja està": és -(punt_d_insercio) - 1. A l'exemple, -4 significa que 400 aniria a l'índex 3. Si vols el punt d'inserció: int pos = -resultat - 1;.

fill

Omple tot l'array (o un rang) amb el mateix valor:

double[] multes = new double[5];
Arrays.fill(multes, -1.0);                       // marca "sense calcular" a totes les celles
Arrays.fill(multes, 0, 2, 0.0);                  // nomes indexs 0 i 1 (el 2 queda fora)

copyOf i copyOfRange

copyOf crea un array nou de la mida indicada, copiant el que hi càpiga i omplint la resta amb el valor per defecte:

Material[] cataleg = new Material[3];
// ... s'omplen les 3 celles ...

Material[] ampliat  = Arrays.copyOf(cataleg, 6);     // 3 originals + 3 nulls
Material[] retallat = Arrays.copyOf(cataleg, 2);     // nomes les 2 primeres

Aquest és exactament el truc que BiblioTech fa servir des del mòdul 3 per "fer créixer" un array, i també, literalment, el que fa ArrayList per dins (05-03).

copyOfRange copia un tram, amb el límit inferior inclòs i el superior exclòs:

int[] nombres = { 10, 20, 30, 40, 50 };
int[] centre  = Arrays.copyOfRange(nombres, 1, 4);    // [20, 30, 40]

equals enfront de deepEquals

Aquí hi ha un parany que costa hores a molta gent. Com que un array no sobreescriu equals, comparar dos arrays amb == o amb .equals() compara referències, no continguts:

int[] a = { 1, 2, 3 };
int[] b = { 1, 2, 3 };

System.out.println(a == b);              // false
System.out.println(a.equals(b));         // false  <- l'equals heretat d'Object
System.out.println(Arrays.equals(a, b)); // true   <- el que gairebe sempre volies

I per a arrays de més d'una dimensió, Arrays.equals compara les files per referència i també falla; cal deepEquals:

int[][] m1 = { { 1, 2 }, { 3, 4 } };
int[][] m2 = { { 1, 2 }, { 3, 4 } };

System.out.println(Arrays.equals(m1, m2));      // false
System.out.println(Arrays.deepEquals(m1, m2));  // true

El mateix passa amb el hash: fes servir Arrays.hashCode i Arrays.deepHashCode, mai array.hashCode(), si vols posar arrays en estructures basades en hash (que a més és una idea que 05-05 et desaconsellarà amb arguments).

asList i el seu parany

Arrays.asList crea una List a partir d'un array, i és temptador perquè sembla la conversió òbvia. Però retorna una vista de mida fixa recolzada en l'array original, no una llista normal:

String[] noms = { "Marta Ruiz", "Diego Alonso", "Nuria Vidal" };
List<String> llista = Arrays.asList(noms);

System.out.println(llista.get(0));       // Marta Ruiz
llista.set(0, "Marta R.");               // permes: canvia TAMBE noms[0]
System.out.println(noms[0]);             // Marta R.

llista.add("Nou");                       // UnsupportedOperationException
llista.remove(0);                        // UnsupportedOperationException
Operació sobre Arrays.asList(array) Resultat
get, size, contains, indexOf, recórrer Funciona amb normalitat
set(i, valor) Funciona i modifica l'array original
add, remove, clear UnsupportedOperationException

Si vols una llista de debò, independent i modificable, embolcalla la vista:

List<String> modificable = new ArrayList<>(Arrays.asList(noms));
modificable.add("Nou");                  // ara si

I un parany addicional que apareix amb primitius: Arrays.asList és genèric i els genèrics no accepten tipus primitius, així que un int[] s'interpreta com un únic element:

int[] nombres = { 1, 2, 3 };
List<int[]> estrany = Arrays.asList(nombres);    // llista d'UN element (l'array sencer)
System.out.println(estrany.size());              // 1, no 3

Amb Integer[] funcionaria com esperes. La causa profunda —els genèrics només operen sobre tipus referència— s'explica a 10-01.

  1. Copiar arrays

Hi ha quatre formes de copiar, i triar malament és una font habitual d'errors subtils.

Material[] original = { llibre1, llibre2, llibre3 };

Material[] a = original;                                       // NO es una copia: es un alias
Material[] b = original.clone();                               // copia superficial
Material[] c = Arrays.copyOf(original, original.length);       // copia superficial
Material[] d = new Material[3];
System.arraycopy(original, 0, d, 0, 3);                        // copia superficial, amb control
Forma Què fa Quan fer-la servir
b = a No copia res. Dues variables, un sol array Mai, si la teva intenció era copiar
a.clone() Còpia superficial de la mateixa mida Còpia ràpida idèntica
Arrays.copyOf(a, n) Còpia superficial amb mida nova Créixer, retallar, còpia defensiva
System.arraycopy(src, iSrc, dst, iDst, n) Copia n elements d'un array a un altre existent Inserir, desplaçar, control fi

La primera fila és l'error de bulto: Material[] a = original; crea un segon nom per al mateix array. Escriure a[0] = altre; canvia també original[0]. És l'aliasing de 03-02 aplicat a arrays, i per això el constructor de Cataleg fa Arrays.copyOf(materials, materials.length): còpia defensiva (03-07) perquè qui va construir l'array no pugui alterar el catàleg per la porta del darrere.

Totes les còpies són superficials (shallow). Copien les referències, no els objectes:

Material[] copia = original.clone();
copia[0].prestar();                      // afecta EL MATEIX Llibre que veu 'original'
System.out.println(original[0].estaDisponible());   // false

Per a una còpia profunda cal clonar element a element, i per això la immutabilitat de 03-07 és tan còmoda: si els objectes no canvien, la còpia superficial és suficient i no hi ha cap risc.

System.arraycopy és el més incòmode dels quatre però el més potent, perquè copia sobre un array que ja existeix, a la posició que vulguis, i admet solapament amb el mateix array d'origen i destinació. És el que fa servir ArrayList.remove(int) per tapar el forat:

// Eliminar l'element de la posicio 1 desplacant els seguents cap a l'esquerra
Material[] c = { m0, m1, m2, m3 };
System.arraycopy(c, 2, c, 1, c.length - 2);   // copia [2..3] sobre [1..2]
c[c.length - 1] = null;                       // allibera l'ultima cella

És una operació O(n): cal moure tots els elements posteriors. Recorda aquest detall, perquè és exactament el cost que pagarà ArrayList.remove(0) a 05-03 i el que LinkedList evita a 05-04.

  1. Varargs per dins és un array

A 03-03 vas fer servir varargs per escriure mètodes amb un nombre variable d'arguments:

public static double sumarMultes(double... multes) {
    double total = 0;
    for (double m : multes) { total += m; }      // es recorre com un array... perque HO ES
    return total;
}

El secret és que double... multes és exactament double[] multes amb sucre sintàctic a la crida: el compilador empaqueta els arguments solts en un array abans d'invocar el mètode.

sumarMultes(3.75, 1.25, 12.50);            // el compilador crea new double[]{3.75, 1.25, 12.50}
sumarMultes();                             // crea new double[0]: array BUIT, no null
sumarMultes(new double[] { 3.75, 1.25 });  // tambe val: li passes l'array directament

D'aquí se'n deriven tres regles pràctiques:

  • Un paràmetre varargs mai no és null si es crida amb la sintaxi normal: sense arguments hi arriba un array de longitud 0. El pots recórrer sense comprovar res.
  • Només n'hi pot haver un i ha d'anar l'últim: metode(String etiqueta, double... valors) és vàlid; a l'inrevés, no.
  • Li pots passar un array ja construït, cosa que resulta molt útil per reenviar arguments entre mètodes.

  1. Aplicació: el catàleg de BiblioTech

Reunim-ho tot al catàleg del projecte, encara en versió array, i ordenem-lo amb els Comparator del mòdul 4.

package com.nexussoftware.bibliotech.servei;

import java.util.Arrays;
import java.util.Comparator;
import com.nexussoftware.bibliotech.domini.Material;

/** Cataleg de BiblioTech en la seva ultima versio basada en arrays. */
public class CatalegArray {

    private Material[] materials;     // array intern
    private int        n;             // quantes celles estan realment ocupades

    public CatalegArray(int capacitatInicial) {
        this.materials = new Material[Math.max(capacitatInicial, 1)];
        this.n = 0;
    }

    /** Afegeix un material, ampliant l'array si esta ple. */
    public void afegir(Material m) {
        if (m == null) { return; }
        if (n == materials.length) {
            // l'array esta ple: en creem un del doble i copiem
            materials = Arrays.copyOf(materials, materials.length * 2);
        }
        materials[n] = m;
        n++;
    }

    /** Elimina per referencia, tapant el forat amb System.arraycopy. */
    public boolean eliminar(String referencia) {
        for (int i = 0; i < n; i++) {
            if (materials[i].getReferencia().equals(referencia)) {
                System.arraycopy(materials, i + 1, materials, i, n - i - 1);
                materials[n - 1] = null;       // evita una fuita de memoria
                n--;
                return true;
            }
        }
        return false;
    }

    /** Retorna una COPIA amb nomes les celles ocupades: copia defensiva. */
    public Material[] llistar() {
        return Arrays.copyOf(materials, n);
    }

    /** Ordena el cataleg amb qualsevol criteri, nomes el tram ocupat. */
    public void ordenar(Comparator<Material> criteri) {
        Arrays.sort(materials, 0, n, criteri);
    }

    public int mida() { return n; }
}

I l'ús, aplicant els comparadors de 04-06:

CatalegArray cataleg = new CatalegArray(4);
cataleg.afegir(new Llibre("Java Eficac",        "Joshua Bloch",  "978-0000000001", 2018));
cataleg.afegir(new Llibre("Patrons de Disseny", "Erich Gamma",   "978-0000000002", 1994));
cataleg.afegir(new Llibre("Refactoritzacio",    "Martin Fowler", "978-0000000003", 1999));
cataleg.afegir(new Revista("Java Magazine",     "REV-2024-03",   42, "Mensual"));
cataleg.afegir(new Dvd("Refactoritzacio en directe", "DVD-0007", 95));   // aqui s'amplia sol

cataleg.ordenar(Comparator.comparing(Material::getTipus)
                          .thenComparing(Material::getTitol));

for (Material m : cataleg.llistar()) {
    System.out.printf("%-10s %-24s %s%n", m.getTipus(), m.getTitol(), m.getReferencia());
}
DVD        Refactoritzacio en directe DVD-0007
Llibre     Java Eficac              978-0000000001
Llibre     Patrons de Disseny       978-0000000002
Llibre     Refactoritzacio          978-0000000003
Revista    Java Magazine            REV-2024-03

Funciona. I conté, escrites a mà, tres peces que a la lliçó següent et donarà el JDK ja fetes: el creixement automàtic, el desplaçament en esborrar i la distinció entre capacitat i mida ocupada.

  1. Les limitacions que motiven les col·leccions

Mira CatalegArray amb ull crític. Tot el que té de més respecte a un List és infraestructura, no lògica de negoci:

Limitació de l'array Què t'obliga a escriure Què fa la col·lecció
Mida fixa Portar un comptador n, comprovar n == length, doblar i copiar add i prou: creix sola
Sense esborrat System.arraycopy per tapar el forat i posar null al final remove
Sense cerca per criteri Un bucle amb if per cada criteri nou contains, indexOf, removeIf
Capacitat ≠ mida Recordar que length no és "quants n'hi ha" i fer copyOf(a, n) en retornar size() és la veritat
Sense garantia d'unicitat Comprovar a mà si un ISBN ja existeix, amb un bucle O(n) Set (05-06)
Sense accés per clau Bucle O(n) per referència; pitjor, bucles imbricats O(n²) per agrupar Map (05-05)
Cap semàntica Una "cua" és un array més dos índexs que mantens tu Queue, Deque (05-07, 05-08)

Cap d'aquestes limitacions converteix l'array en una cosa obsoleta. Un array continua sent l'opció correcta quan:

  • La mida és coneguda i fixa (una matriu 3×12, un tauler, una memòria intermèdia de 4096 bytes).
  • Treballes amb primitius i el rendiment importa: un int[] d'un milió d'elements ocupa 4 MB; un List<Integer> pot ocupar cinc vegades més per l'autoboxing (05-02).
  • Necessites màxima velocitat de recorregut i la localitat de memòria cau és determinant.
  • Estàs implementant una estructura de dades, com fan ArrayList, HashMap i ArrayDeque per dins.

Per a tota la resta —que en una aplicació de gestió és pràcticament tot— la resposta és el Framework de Col·leccions.

Errors Habituals i Consells

length amb parèntesis. array.length() no compila. És un atribut en arrays, un mètode a String i size() a les col·leccions. Tingue-ho al cap com una taula de tres files.

Confondre capacitat amb contingut. new Material[10] té length == 10 però zero materials. Si només n'has omplert tres cel·les, recórrer les deu et donarà set NullPointerException. Porta sempre un comptador o retalla amb Arrays.copyOf(array, n) abans de retornar.

Imprimir un array directament. System.out.println(array) mostra [I@1b6d3586. Fes servir Arrays.toString(array) i, amb més d'una dimensió, Arrays.deepToString(array).

Comparar arrays amb equals o ==. Tots dos comparen referències. Fes servir Arrays.equals i, amb més dimensions, Arrays.deepEquals.

Creure que el for-each modifica l'array. for (int n : nombres) { n = 0; } no canvia res: n és una còpia. Si escrius a les cel·les, fes servir el for clàssic. Si només crides mètodes de l'objecte, el for-each va perfecte.

Assignar en lloc de copiar. Material[] copia = original; no copia; crea un àlies. Fes servir original.clone() o Arrays.copyOf(original, original.length). I recorda que totes dues són superficials: els objectes apuntats es comparteixen.

Arrays.asList com si fos una llista normal. És una vista de mida fixa recolzada en l'array: set sí, add/remove llancen UnsupportedOperationException. Si necessites modificar-la, embolcalla-la en new ArrayList<>(...).

binarySearch sobre un array desordenat. Retorna resultats sense sentit, sense cap avís. Ordena primer, amb el mateix criteri amb què hi busques. I recorda que el negatiu retornat és -(punt d'inserció) - 1, no un simple "no hi és".

Oblidar posar a null la cel·la alliberada en esborrar. A eliminar, després de l'arraycopy, l'última cel·la continua apuntant a l'objecte que ja no forma part del catàleg. Mentre l'array visqui, aquest objecte no es pot recollir: és una fuita de memòria silenciosa. ArrayList fa exactament aquest elementData[--size] = null pel mateix motiu.

Consell d'estil: for-each per defecte. Si el bucle no necessita l'índex, escriu for (Material m : cataleg). És més curt, més clar i impossible de trencar pels límits. Baixa al for clàssic només quan l'índex sigui realment necessari.

Exercicis

Exercici 1: estadístiques del catàleg

Escriu una classe EstadistiquesCataleg amb mètodes estàtics que rebin un Material[]:

  • int comptarPerTipus(Material[] cataleg, String tipus): quants materials d'aquell tipus hi ha.
  • Material mesCar(Material[] cataleg): el de tarifa diària més alta, o null si l'array és buit o és null.
  • double tarifaMitjana(Material[] cataleg): mitjana de les tarifes diàries, 0.0 si no hi ha elements.
  • String[] titols(Material[] cataleg): un array nou només amb els títols.

Tots han d'ignorar les cel·les a null i fer servir for-each sempre que sigui possible.

Exercici 2: informe mensual amb array bidimensional

Crea InformeMensual que gestioni un int[][] prestecs de 3 files (Llibre, Revista, DVD) per 12 columnes (mesos). Implementa:

  • void registrar(int tipus, int mes): incrementa la cel·la corresponent.
  • int totalPerTipus(int tipus) i int totalPerMes(int mes).
  • int mesMesActiu(): l'índex del mes amb més préstecs totals.
  • String taula(): una taula formatada amb printf que mostri files, columnes i totals.

Exercici 3: caixa d'eines amb Arrays

Escriu UtilCataleg amb mètodes estàtics que apliquin la classe Arrays:

  • Material[] afegir(Material[] cataleg, Material nou): retorna un array nou amb un element més (sense modificar l'original).
  • Material[] eliminarA(Material[] cataleg, int index): retorna un array nou sense aquell element.
  • Material[] ordenatPerTitol(Material[] cataleg): retorna una còpia ordenada, deixant intacte l'original.
  • int cercarPerReferencia(Material[] cataleg, String referencia): fes servir Arrays.sort + Arrays.binarySearch sobre una còpia i explica en un comentari per què l'índex retornat no serveix per a l'array original.

Solucions

Solució 1

package com.nexussoftware.bibliotech.servei;

import com.nexussoftware.bibliotech.domini.Material;

public final class EstadistiquesCataleg {

    private EstadistiquesCataleg() { }     // classe d'utilitats: no s'instancia

    public static int comptarPerTipus(Material[] cataleg, String tipus) {
        if (cataleg == null || tipus == null) { return 0; }
        int n = 0;
        for (Material m : cataleg) {                    // for-each: no necessitem l'index
            if (m != null && m.getTipus().equals(tipus)) {   // equals, no ==, per a cadenes (01-05)
                n++;
            }
        }
        return n;
    }

    public static Material mesCar(Material[] cataleg) {
        if (cataleg == null) { return null; }
        Material millor = null;
        for (Material m : cataleg) {
            if (m == null) { continue; }
            // la primera vegada 'millor' es null: cal tractar-ho a part
            if (millor == null || m.getTarifaDiaria() > millor.getTarifaDiaria()) {
                millor = m;
            }
        }
        return millor;
    }

    public static double tarifaMitjana(Material[] cataleg) {
        if (cataleg == null) { return 0.0; }
        double suma = 0.0;
        int    n    = 0;                      // comptem NOMES els no nuls
        for (Material m : cataleg) {
            if (m != null) { suma += m.getTarifaDiaria(); n++; }
        }
        return (n == 0) ? 0.0 : suma / n;     // proteccio contra la divisio per zero
    }

    public static String[] titols(Material[] cataleg) {
        if (cataleg == null) { return new String[0]; }   // no retornis mai null: array buit
        String[] resultat = new String[cataleg.length];  // mida MAXIMA possible
        int n = 0;
        for (Material m : cataleg) {
            if (m != null) { resultat[n++] = m.getTitol(); }
        }
        // retallem al realment usat: el patro que ArrayList fara innecessari
        return java.util.Arrays.copyOf(resultat, n);
    }
}

Els quatre mètodes comparteixen el mateix esquelet defensiu: comprovar null a l'array, saltar-se els elements null i retornar un valor neutre (0, null documentat, array buit) quan no hi ha dades. El detall important de titols és el patró "crear de la mida màxima, comptar, retallar": és l'única manera de retornar un array de la mida exacta quan no saps per endavant quants elements hi haurà. És també, exactament, el que fa ArrayList.toArray() per tu.

Solució 2

package com.nexussoftware.bibliotech.presentacio;

public class InformeMensual {

    private static final String[] TIPUS = { "Llibres", "Revistes", "DVDs" };
    private static final String[] MESOS = { "Gen", "Feb", "Mar", "Abr", "Mai", "Jun",
                                            "Jul", "Ago", "Set", "Oct", "Nov", "Des" };

    private final int[][] prestecs = new int[TIPUS.length][MESOS.length];   // 3 x 12, tot a 0

    public void registrar(int tipus, int mes) {
        // comprovem limits a ma: el modul 6 ensenyara a senyalar-ho amb una excepcio
        if (tipus < 0 || tipus >= TIPUS.length || mes < 0 || mes >= MESOS.length) {
            System.out.println("AVIS: index fora de rang, registre ignorat");
            return;
        }
        prestecs[tipus][mes]++;
    }

    public int totalPerTipus(int tipus) {
        int total = 0;
        for (int valor : prestecs[tipus]) {    // recorrem una FILA sencera: es un int[]
            total += valor;
        }
        return total;
    }

    public int totalPerMes(int mes) {
        int total = 0;
        for (int[] fila : prestecs) {          // recorrem les files i en prenem una columna
            total += fila[mes];
        }
        return total;
    }

    public int mesMesActiu() {
        int millorMes = 0;
        int millorTotal = totalPerMes(0);
        for (int mes = 1; mes < MESOS.length; mes++) {   // for classic: necessitem l'index
            int total = totalPerMes(mes);
            if (total > millorTotal) { millorTotal = total; millorMes = mes; }
        }
        return millorMes;
    }

    public String taula() {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("%-10s", ""));
        for (String mes : MESOS) { sb.append(String.format("%5s", mes)); }
        sb.append(String.format("%8s%n", "TOTAL"));

        for (int t = 0; t < TIPUS.length; t++) {
            sb.append(String.format("%-10s", TIPUS[t]));
            for (int valor : prestecs[t]) { sb.append(String.format("%5d", valor)); }
            sb.append(String.format("%8d%n", totalPerTipus(t)));
        }

        sb.append(String.format("%-10s", "TOTAL"));
        for (int mes = 0; mes < MESOS.length; mes++) {
            sb.append(String.format("%5d", totalPerMes(mes)));
        }
        sb.append(String.format("%8d%n", totalGeneral()));
        sb.append("Mes mes actiu: ").append(MESOS[mesMesActiu()]).append('\n');
        return sb.toString();
    }

    private int totalGeneral() {
        int total = 0;
        for (int[] fila : prestecs) {
            for (int valor : fila) { total += valor; }
        }
        return total;
    }
}

Observa l'alternança deliberada entre els dos bucles. totalPerTipus recorre una fila amb for-each perquè no necessita saber quin mes és cada valor. totalPerMes recorre les files amb for-each però indexa la columna, perquè el mes ve donat. I mesMesActiu necessita el for clàssic perquè el que retorna és l'índex. Cada bucle fa servir l'eina que li correspon.

Solució 3

package com.nexussoftware.bibliotech.servei;

import java.util.Arrays;
import java.util.Comparator;
import com.nexussoftware.bibliotech.domini.Material;

public final class UtilCataleg {

    private UtilCataleg() { }

    /** Retorna un array NOU amb un element mes. L'original no es toca. */
    public static Material[] afegir(Material[] cataleg, Material nou) {
        if (cataleg == null) { return new Material[] { nou }; }
        // copyOf amb length+1 crea l'array ampliat amb l'ultima cella a null
        Material[] ampliat = Arrays.copyOf(cataleg, cataleg.length + 1);
        ampliat[cataleg.length] = nou;
        return ampliat;
    }

    /** Retorna un array NOU sense l'element d'aquella posicio. */
    public static Material[] eliminarA(Material[] cataleg, int index) {
        if (cataleg == null || index < 0 || index >= cataleg.length) {
            return cataleg;                        // res a fer
        }
        Material[] resultat = new Material[cataleg.length - 1];
        // dues copies: el tram anterior a l'index i el posterior
        System.arraycopy(cataleg, 0, resultat, 0, index);
        System.arraycopy(cataleg, index + 1, resultat, index,
                         cataleg.length - index - 1);
        return resultat;
    }

    /** Copia ordenada per titol. L'original conserva el seu ordre. */
    public static Material[] ordenatPerTitol(Material[] cataleg) {
        if (cataleg == null) { return new Material[0]; }
        Material[] copia = cataleg.clone();         // clone: copia superficial de la mateixa mida
        Arrays.sort(copia, Comparator.comparing(Material::getTitol));
        return copia;                               // els Material son els MATEIXOS objectes
    }

    /**
     * Cerca per referencia amb cerca binaria sobre una copia ordenada.
     *
     * IMPORTANT: l'index retornat per binarySearch es la posicio a la COPIA
     * ORDENADA, no a l'array original. Per aixo no el retornem: recuperem el
     * material trobat i busquem LA SEVA posicio real a l'original. Confondre
     * tots dos indexs es un dels errors mes subtils treballant amb arrays.
     */
    public static int cercarPerReferencia(Material[] cataleg, String referencia) {
        if (cataleg == null || referencia == null) { return -1; }

        Comparator<Material> perReferencia = Comparator.comparing(Material::getReferencia);
        Material[] copia = cataleg.clone();
        Arrays.sort(copia, perReferencia);

        // binarySearch necessita un "element sonda" amb la referencia buscada
        Material sonda = new Llibre("", "", referencia, 2000);
        int posEnCopia = Arrays.binarySearch(copia, sonda, perReferencia);
        if (posEnCopia < 0) { return -1; }          // negatiu = punt d'insercio, no hi es

        Material trobat = copia[posEnCopia];
        for (int i = 0; i < cataleg.length; i++) {  // traduim a la posicio real
            if (cataleg[i] == trobat) { return i; }
        }
        return -1;
    }
}

Aquest exercici deixa al descobert com d'incòmode resulta l'array per a operacions quotidianes. afegir construeix un array sencer per posar-hi un element: O(n) cada vegada. eliminarA necessita dos arraycopy. I cercarPerReferencia costa més que la cerca lineal que pretenia evitar, perquè ordenar la còpia és O(n log n) i després cal traduir l'índex. La conclusió honesta és que per buscar per clau, l'array és l'estructura equivocada: el correcte és un Map<String, Material>, amb cerca O(1) i sense còpies ni sondes. És la lliçó 05-05.

Conclusió

Ja domines l'estructura sobre la qual està construït tot la resta. Saps que un array és un bloc contigu de memòria de mida fixa i tipus homogeni, que és un objecte del heap encara que no tingui cap classe visible, i que la seva contigüitat li dona accés O(1) per índex i una localitat de memòria cau que cap altra estructura no iguala. Distingeixes declarar, crear amb new i inicialitzar amb literal; coneixes els valors per defecte i saps que new Material[10] conté deu null, no deu materials; i tens clar que length és un atribut, que els índexs van de 0 a length - 1 i que sortir-se'n llança ArrayIndexOutOfBoundsException —una excepció que el mòdul 6 t'ensenyarà a tractar.

Has après per fi el for-each que va quedar ajornat al mòdul 2: la seva sintaxi for (Tipus e : origen), els seus avantatges —menys codi, impossible sortir-se de rang, mateixa forma per a arrays i col·leccions— i les seves tres limitacions exactes: no dona l'índex, no pot reassignar la cel·la (encara que sí modificar l'objecte apuntat) i només va cap endavant. I tens el criteri: for-each per defecte, for clàssic quan l'índex sigui imprescindible.

Saps què guarda realment cada cel·la —valors als arrays de primitius, referències als d'objectes—, amb tot el que se'n deriva: aliasing, null, pitjor localitat i la covariància que pot llançar ArrayStoreException en execució, el forat que els genèrics de 10-01 van venir a tapar. Manegues arrays multidimensionals entenent-los com a arrays d'arrays, inclosos els dentats amb files de longituds diferents. I tens la classe Arrays a les mans: toString/deepToString per depurar, sort amb i sense Comparator, binarySearch amb els seus dos advertiments, fill, copyOf, copyOfRange, equals/deepEquals enfront de l'equals heretat que compara referències, i asList amb el seu parany de vista de mida fixa. Saps copiar de quatre maneres, i que totes són superficials. I saps que un paràmetre varargs és literalment un array, mai null, sempre l'últim.

BiblioTech té ara un CatalegArray que creix sol, esborra tapant el forat, retorna còpies defensives i s'ordena amb qualsevol Comparator del mòdul 4. Funciona. I el seu codi és, en tres quartes parts, infraestructura que no hi hauria de ser: un comptador n paral·lel al length, un Arrays.copyOf per doblar la capacitat, un System.arraycopy per desplaçar, un null manual per no filtrar memòria. Res d'això no parla de biblioteques ni de préstecs. A més, continua sense poder garantir que no hi hagi ISBN duplicats sense un bucle O(n), i sense poder buscar per referència en menys de O(n) ni agrupar préstecs per empleat sense bucles imbricats O(n²).

A la lliçó següent, El framework de col·leccions, veuràs el mapa complet de les estructures que el JDK ja porta resoltes: la jerarquia Iterable → Collection → List/Set/Queue, el Map que va a part i per què, la regla d'or «declara per la interfície, instancia la implementació», la taula mestra que compara les deu implementacions que faràs servir la resta de la teva vida professional, com funciona per dins el for-each que acabes d'aprendre —amb l'Iterator que hi ha a sota i la temuda ConcurrentModificationException— i un arbre de decisió per triar la col·lecció correcta a la primera. A partir d'aquí, cada lliçó del mòdul desenvoluparà una fila d'aquesta taula, i el CatalegArray que acabes d'escriure es quedarà en la meitat de línies.

Curs de Programació en Java

Mòdul 1: Introducció a Java

Mòdul 2: Flux de control

Mòdul 3: Programació orientada a objectes

Mòdul 4: Programació orientada a objectes avançada

Mòdul 5: Estructures de dades i col·leccions

Mòdul 6: Gestió d'excepcions

Mòdul 7: Entrada/sortida de fitxers

Mòdul 8: Multifil i concurrència

Mòdul 9: Xarxes

Mòdul 10: Temes avançats

Mòdul 11: Frameworks i llibreries de Java

Mòdul 12: Construcció d'aplicacions del món real

© Copyright 2026. Tots els drets reservats