9  Arrays i Strings

Objectius

  • Entendre què és un array i quan et cal.
  • Declarar, crear i inicialitzar arrays d’una dimensió (1D).
  • Recórrer arrays amb for i amb el bucle for-each.
  • Fer cerques (lineal) i ordenacions senzilles.
  • Treballar amb arrays de dues dimensions (2D), com una graella.
  • Manipular text amb la classe String i els seus mètodes.
  • Construir text de manera eficient amb StringBuilder.
  • Fer una primera passa per les expressions regulars (regex).

Imagina que has de guardar les notes de 30 alumnes. ¿Declararàs 30 variables (nota1, nota2, …, nota30)? Seria una bogeria: no podries recórrer-les amb un bucle ni fer-hi càlculs còmodament. La solució són els arrays: una manera de guardar moltes dades del mateix tipus sota un únic nom. I com que el text és un cas particular de col·lecció de caràcters, aprofitarem per dominar els Strings.

9.1 Què és un array

Un array (o vector) és un contenidor de mida fixa que guarda un conjunt de valors del mateix tipus, col·locats en posicions consecutives i numerades.

NotaAnalogia: un bloc de bústies

Un array és com el bloc de bústies de l’entrada d’un edifici: una fila de calaixos idèntics, tots del mateix tipus, cadascun amb un número. Per accedir al contingut d’un calaix, no necessites obrir-los tots: només has de saber el seu número. Aquest número és l’índex.

La característica més important —i la que més errors provoca al principi— és que els índexs comencen a 0.

Un array de 5 elements té els índexs 0, 1, 2, 3 i 4. No existeix la posició 5.

%%{init: {"themeCSS": ".cluster rect { fill: transparent; stroke: none; }"}}%%
flowchart LR
    %% subgraph notes["array notes (5 elements)"]
    %% direction LR
    A["7<br/>índex 0"] --- B["4<br/>índex 1"] --- C["9<br/>índex 2"] --- D["6<br/>índex 3"] --- E["8<br/>índex 4"]
    %% end

9.2 Arrays d’una dimensió

9.2.1 Declarar i crear

Hi ha dues maneres de posar en marxa un array:

// Manera 1: crear-lo buit amb una mida fixa (s'omple de zeros)
int[] notes = new int[5];       // 5 enters, tots valen 0 de moment

// Manera 2: crear-lo ja amb valors coneguts
int[] edats = {18, 20, 19, 22, 21};

Quan crees un array amb new int[5], Java el reserva ple de valors per defecte: 0 per als enters, 0.0 per als decimals, false per als booleans i null per als objectes i String.

9.2.2 Accedir i modificar

S’accedeix a cada element amb el nom de l’array i l’índex entre claudàtors [ ]:

int[] notes = {7, 4, 9, 6, 8};

System.out.println(notes[0]);   // 7  (primer element)
System.out.println(notes[2]);   // 9  (tercer element)

notes[1] = 10;                  // canviem el segon element
System.out.println(notes[1]);   // 10

Tot array coneix la seva mida gràcies a .length (fixa’t: sense parèntesis, no és un mètode):

System.out.println("Té " + notes.length + " elements.");   // 5
AlertaError típic: sortir-se de l’array

Aquest és el clàssic. Un array de 5 elements té índexs de 0 a 4. Si intentes accedir a notes[5], Java llança una ArrayIndexOutOfBoundsException (recorda Capítol 8):

int[] notes = new int[5];
notes[5] = 100;   // ERROR! l'últim índex vàlid és 4

Regla d’or: l’últim índex vàlid sempre és length - 1.

9.2.3 Recórrer un array

Aquí és on els arrays brillen: combinats amb un bucle (Capítol 7), pots processar tots els elements amb poques línies. Fes servir .length com a límit:

int[] notes = {7, 4, 9, 6, 8};
for (int i = 0; i < notes.length; i++) {
    System.out.println("Nota " + i + ": " + notes[i]);
}

Quan només vols llegir els valors i no et cal l’índex, hi ha una forma més neta: el bucle for-each. Es llegeix “per a cada nota dins de notes”:

for (int nota : notes) {
    System.out.println(nota);
}

Compara les dues formes de recórrer:

Situació for clàssic for-each
Tens l’índex? Sí (i) No
Pots modificar l’array? Sí (notes[i] = ...) No (només llegeixes)
Recórrer al revés o saltant? No
Llegibilitat quan només llegeixes Bona Excel·lent
ConsellMini-repte

Donat un array de temperatures {22, 19, 25, 30, 18}, calcula i mostra la mitjana, la temperatura màxima i la mínima. Pista: recorre l’array amb un for i ves actualitzant tres variables.

9.3 Cerca i ordenació

Dues de les tasques més habituals amb arrays són buscar un element i ordenar-los.

9.3.1 Cerca lineal

La cerca més senzilla és la lineal: mirar element per element fins a trobar el que busques (o arribar al final).

int[] edats = {18, 20, 19, 22, 21};
int busca = 22;
int posicio = -1;   // -1 vol dir "no trobat"

for (int i = 0; i < edats.length; i++) {
    if (edats[i] == busca) {
        posicio = i;
        break;      // ja l'hem trobat, no cal seguir (@sec-repeticio)
    }
}

if (posicio != -1) {
    System.out.println("Trobat a la posició " + posicio);
} else {
    System.out.println("No hi és.");
}

9.3.2 Ordenació

Ordenar a mà (amb algorismes com el bubble sort) és un exercici clàssic molt útil per entendre bucles niats. Però a la pràctica, Java ja et dona l’ordenació feta amb Arrays.sort:

import java.util.Arrays;

int[] numeros = {5, 2, 9, 1, 7};
Arrays.sort(numeros);
System.out.println(Arrays.toString(numeros));   // [1, 2, 5, 7, 9]
NotaArrays.toString

Si escrius System.out.println(numeros) directament, veuràs una cosa lletja com [I@1b6d3586: la direcció de memòria, no el contingut. Per veure els valors, fes servir Arrays.toString(numeros).

ConsellMini-repte

Crea un array amb 6 nombres desordenats. Ordena’l amb Arrays.sort i després busca amb cerca lineal si hi ha el nombre 7.

9.4 Arrays de dues dimensions

Un array 1D és una fila. Un array 2D és una graella (files i columnes): perfecte per a taulers de joc, matrius, fulls de càlcul o mapes. És, en essència, “un array d’arrays”.

// 3 files, 4 columnes, tot a 0
int[] filaUnica;                 // 1D, per comparar
int[][] taula = new int[3][4];   // 2D

// Amb valors inicials
int[][] tauler = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

S’accedeix amb dos índexs: tauler[fila][columna].

System.out.println(tauler[0][0]);       // 1: cantonada superior esquerra
System.out.println(tauler[1][2]);       // 6 : fila 1, columna 2
tauler[2][0] = 70;                      // Modifiquem la cantonada inferior

flowchart TD
    subgraph G["tauler[fila][columna]"]
    direction TB
    R0["[0][0]=1 · [0][1]=2 · [0][2]=3"]
    R1["[1][0]=4 · [1][1]=5 · [1][2]=6"]
    R2["[2][0]=7 · [2][1]=8 · [2][2]=9"]
    end

tauler 0 1 2
0 1 2 3
1 4 5 6
2 70 8 9

Per recórrer una graella necessites bucles aniuats: un per a les files i un altre per a les columnes.

for (int fila = 0; fila < tauler.length; fila++) {          // nombre de files
    for (int col = 0; col < tauler[fila].length; col++) {   // columnes d'aquesta fila
        System.out.print(tauler[fila][col] + " ");
    }
    System.out.println();   // salt de línia en acabar cada fila
}
AlertaError típic: confondre files i columnes

tauler.length et dona el nombre de files. Per saber les columnes d’una fila, has de mirar tauler[fila].length. Intercanviar els dos índexs (tauler[col][fila]) és un error molt freqüent que sovint provoca una ArrayIndexOutOfBoundsException.

ConsellMini-repte

Crea un tauler 3×3 de tres en ratlla amb caràcters (char[][]), omple’l de guions - i col·loca-hi una X al centre. Mostra’l per pantalla amb bucles niats.

9.5 La classe String

Un String és una cadena de text. Ja l’has fet servir des del primer programa, però ara veuràs que és un objecte ple de mètodes útils. Internament, un String és una seqüència de caràcters (gairebé un array de char), per això molts conceptes d’aquest capítol s’hi apliquen.

String nom = "Programació";

System.out.println(nom.length());              // 11  (nombre de caràcters)
System.out.println(nom.charAt(0));             // P   (caràcter a la posició 0)
System.out.println(nom.toUpperCase());         // PROGRAMACIÓ
System.out.println(nom.toLowerCase());         // programació
System.out.println(nom.substring(0, 4));       // Prog (de 0 a 3)
System.out.println(nom.indexOf("gram"));       // 3   (on comença)
System.out.println(nom.contains("gra"));       // true
System.out.println(nom.replace("ó", "o"));     // Programacio

Els mètodes de String més útils:

Mètode Què fa
length() Nombre de caràcters
charAt(i) Caràcter a la posició i
substring(inici, fi) Tros de text (fi no inclòs)
indexOf(text) Posició on apareix (o -1)
contains(text) Conté aquest text?
toUpperCase() / toLowerCase() Majúscules / minúscules
trim() Treu espais dels extrems
replace(a, b) Substitueix a per b
split(sep) Parteix el text en un array
equals(altre) Compara continguts
AlertaError típic: comparar Strings amb ==

Per comparar el contingut de dos textos, has d’usar .equals(), mai ==:

String a = "hola";
String b = sc.nextLine();   // L'usuari escriu "hola"

if (a == b) { ... }         // MALAMENT: compara si són el mateix objecte
if (a.equals(b)) { ... }    // BÉ: compara el contingut

Amb == compares si les dues variables apunten exactament al mateix lloc de memòria, no si diuen el mateix. Aquest error dona molts maldecaps. Per ignorar majúscules/minúscules, fes servir equalsIgnoreCase().

Un mètode especialment útil és split, que trosseja un text segons un separador i retorna un array de Strings.

String linia = "Anna;25;Girona";
String[] camps = linia.split(";");
System.out.println(camps[0]);   // Anna
System.out.println(camps[1]);   // 25
System.out.println(camps[2]);   // Girona
ConsellMini-repte

Demana el nom complet d’una persona i mostra les seves inicials. Pista: split(" ") per separar les paraules i charAt(0) per agafar la primera lletra de cadascuna.

9.6 StringBuilder: construir text eficientment

Els Strings a Java són immutables: un cop creats, no es poden canviar. Quan fas text = text + "x", en realitat Java crea un String nou cada vegada. Dins d’un bucle amb moltes voltes, això és molt ineficient.

NotaAnalogia

Modificar un String sumant trossos és com, cada cop que vols afegir una paraula a una carta, escriure tota la carta de nou en un full net. Amb StringBuilder, en canvi, escrius amb llapis sobre el mateix full: afegeixes al final sense reescriure res.

StringBuilder és un text modificable, pensat per construir cadenes peça a peça:

StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 5; i++) {
    sb.append(i).append("-");   // afegeix al final
}
System.out.println(sb.toString());   // 1-2-3-4-5-
Mètode Què fa
append(x) Afegeix x al final
insert(pos, x) Insereix x a la posició
reverse() Inverteix el text
toString() El converteix en String normal
ConsellQuan usar cada un

Per a text senzill o poques concatenacions, String va perfecte i és més llegible. Quan construeixes text dins d’un bucle amb moltes iteracions, fes servir StringBuilder.

9.7 Introducció a les expressions regulars

Una expressió regular (regex) és un patró que descriu la forma que ha de tenir un text. Serveixen per validar (això és un correu vàlid?), buscar i substituir amb regles complexes. Són molt potents, i aquí només en farem una primera ullada.

El mètode matches de String comprova si un text encaixa sencer amb un patró:

String codi = "A1234";
System.out.println(codi.matches("[A-Z][0-9]{4}"));   // true: 1 lletra + 4 xifres

Els símbols bàsics dels patrons:

Símbol Significa Exemple
[0-9] o \\d Una xifra 5
[a-z] Una lletra minúscula k
[A-Z] Una lletra majúscula M
. Qualsevol caràcter qualsevol
+ Un o més cops l’anterior \\d+ = un o més dígits
* Zero o més cops
{n} Exactament n cops [0-9]{4} = 4 xifres
? Opcional (0 o 1 cop)


Exemple: validar un codi postal (5 xifres):

String cp = "17001";
if (cp.matches("\\d{5}")) {
    System.out.println("Codi postal vàlid");
} else {
    System.out.println("Format incorrecte");
}
AlertaError típic: la doble barra invertida

En una regex, \d significa “una xifra”. Però dins d’un String de Java, la barra invertida \ és especial, així que l’has d’escriure duplicada: "\\d".

És el mateix motiu que vam veure amb les rutes de fitxers.

ConsellMini-repte

Escriu un programa que demani un DNI i comprovi si té el format correcte: 8 xifres seguides d’una lletra majúscula. Pista: el patró és \\d{8}[A-Z].

9.8 Resum

  • Un array guarda moltes dades del mateix tipus sota un nom; té mida fixa i els índexs comencen a 0.
  • L’últim índex vàlid és length - 1; sortir-se’n provoca una ArrayIndexOutOfBoundsException.
  • Es recorren amb for (quan cal l’índex o modificar) o amb for-each (quan només es llegeix).
  • La cerca lineal mira element per element; Arrays.sort ordena, i Arrays.toString mostra el contingut.
  • Els arrays 2D són graelles: dos índexs [fila][columna] i bucles niats per recórrer-los.
  • La classe String ofereix molts mètodes (length, substring, split, replace…). Compara sempre amb equals(), mai amb ==.
  • StringBuilder construeix text de manera eficient dins de bucles, perquè els Strings són immutables.
  • Les expressions regulars descriuen patrons de text; recorda duplicar la barra (\\d).

9.9 Per practicar

  1. Estadístiques. Demana 10 notes, guarda-les en un array i mostra mitjana, màxima, mínima i quantes són aprovats.
  2. Invertir. Recorre un array del final al principi per mostrar-lo a l’inrevés, i després crea un array nou amb l’ordre invertit.
  3. Comptar vots. Simula una votació amb un array 1D on cada posició és un candidat i mostra el guanyador.
  4. Tres en ratlla. Fes un tauler 3×3 amb char[][], deixa que dos jugadors hi juguin per consola i detecta el guanyador.
  5. Comptador de vocals. Demana una frase i compta quantes vegades apareix cada vocal (fes servir charAt en un bucle).
  6. Palíndrom. Comprova si una paraula es llegeix igual del dret i del revés (pista: StringBuilder.reverse()).
  7. Validador de dades. Amb regex, valida un correu electrònic senzill, un telèfon de 9 xifres i una matrícula de cotxe (4 xifres + 3 lletres).
  8. Del fitxer a l’array. Llegeix un CSV de socis, separa cada línia amb split(";") i guarda els noms en un array.