La lliçó anterior va acabar amb una frustració concreta: quan BiblioTechApp detecta una dada invàlida, es rendeix i acaba en lloc de tornar a preguntar; i quan acaba de processar una devolució, s'acaba el programa, encara que la Marta Ruiz porti quatre llibres a la motxilla. Les condicionals van ensenyar al teu programa a decidir; els bucles li ensenyen a repetir. Són, juntament amb els condicionals, la meitat del flux de control, i probablement la construcció que més vegades escriuràs a la teva vida professional. En aquesta lliçó veuràs les tres formes clàssiques de bucle en Java, l'anatomia interna de cadascuna, els patrons fonamentals que resolen el 90 % dels casos reals (comptar, acumular, cercar, validar, oferir un menú) i els paranys que converteixen un bucle en una penjada o en un consum desbocat de memòria.
Contingut
- Què és un bucle i quines parts té
- El bucle
while - El bucle
do-while - El bucle
for - Taula comparativa: quan fer servir cadascun
- El bucle
for-each: existeix, però arriba més tard - Bucles imbricats: la taula de multes de BiblioTech
- Patrons fonamentals: comptador, acumulador i cerca amb bandera
- Bucle de validació d'entrada
- Bucle de menú (avançament del projecte)
- Bucles infinits: intencionats i accidentals
- Cost de les iteracions i el parany de concatenar
String - Errors Habituals i Consells
- Exercicis
- Què és un bucle i quines parts té
Un bucle (o loop) és una estructura que executa repetidament un bloc de codi mentre es compleixi una condició. Cada passada completa pel bloc s'anomena iteració.
Tot bucle, sigui de la forma que sigui, té quatre elements. Identificar-los sempre —fins i tot quan el llenguatge no t'obligui a escriure'ls junts— és la millor defensa contra els bucles infinits:
| Element | Funció | Si falta o està malament... |
|---|---|---|
| Inicialització | Prepara les variables de control abans de començar | El bucle arrenca amb brossa o no compila |
| Condició | S'avalua abans (o després) de cada iteració; decideix si continua | Bucle infinit o bucle que no s'executa mai |
| Cos | La feina que es repeteix | El bucle no fa res útil |
| Actualització | Modifica la variable de control per acostar-se al final | Bucle infinit garantit |
L'esquema mental:
flowchart TD
I["Inicialitzacio"] --> C{"Condicio"}
C -- "true" --> B["Cos"]
B --> U["Actualitzacio"]
U --> C
C -- "false" --> F["Continuar despres del bucle"]
- El bucle
while
whilewhile és el bucle més elemental: repeteix mentre la condició sigui certa. Comprova la condició abans de cada iteració, així que pot executar-se zero vegades.
Anatomia dissecada amb un comptador de devolucions processades:
int devolucionsProcessades = 0; // 1. INICIALITZACIÓ (fora del bucle)
while (devolucionsProcessades < 3) { // 2. CONDICIÓ (es comprova ABANS d'entrar)
System.out.println("Processant devolucio numero " + (devolucionsProcessades + 1));
// 3. COS
devolucionsProcessades++; // 4. ACTUALITZACIÓ (dins del cos)
}
System.out.println("Total processades: " + devolucionsProcessades);Sortida:
Processant devolucio numero 1 Processant devolucio numero 2 Processant devolucio numero 3 Total processades: 3
Traça pas a pas, que és com has de llegir sempre un bucle la primera vegada:
| Iteració | devolucionsProcessades en entrar |
< 3 |
Imprimeix | En sortir |
|---|---|---|---|---|
| 1 | 0 | true |
"…numero 1" | 1 |
| 2 | 1 | true |
"…numero 2" | 2 |
| 3 | 2 | true |
"…numero 3" | 3 |
| — | 3 | false |
— | fi |
En aquest bucle, l'actualització és dins del cos i és responsabilitat teva no oblidar-la. Aquest és el gran risc de while: si esborres el devolucionsProcessades++, la condició no canvia mai i el programa es queda penjat imprimint la mateixa línia per sempre.
while brilla quan no saps quantes iteracions hi haurà, perquè el nombre depèn d'alguna cosa externa (el que escrigui l'usuari, si s'ha trobat alguna cosa, si queda feina pendent):
final double MULTA_MAXIMA = 20.0;
final double TARIFA_DIARIA = 0.25;
double multaAcumulada = 0.0;
int dies = 0;
// Quants dies de retard calen per arribar al sostre?
while (multaAcumulada < MULTA_MAXIMA) {
dies++;
multaAcumulada += TARIFA_DIARIA;
}
System.out.printf("Amb %d dies de retard s'assoleix el sostre de %.2f EUR%n",
dies, multaAcumulada);
// Amb 80 dies de retard s'assoleix el sostre de 20,00 EUR
- El bucle
do-while
do-whiledo-while és idèntic a while llevat d'un punt decisiu: comprova la condició al final, per la qual cosa el cos s'executa almenys una vegada, passi el que passi.
do {
// cos: s'executa SEMPRE almenys una vegada
} while (condicio); // atencio al punt i coma final!El punt i coma després del while és obligatori; és l'únic lloc de Java on un while en porta.
flowchart TD
I["Inicialitzacio"] --> B["Cos (sempre almenys una vegada)"]
B --> U["Actualitzacio"]
U --> C{"Condicio"}
C -- "true" --> B
C -- "false" --> F["Continuar despres del bucle"]
La diferència pràctica es veu comparant tots dos amb una condició falsa des del principi:
int diesRetard = 0;
while (diesRetard > 0) {
System.out.println("WHILE: hi ha retard"); // NO s'imprimeix mai
}
do {
System.out.println("DO-WHILE: hi ha retard"); // SI s'imprimeix una vegada
} while (diesRetard > 0);do-while és l'eina natural per a tot el que cal fer almenys una vegada i potser repetir: demanar una dada a l'usuari, mostrar un menú, reintentar una operació. És el bucle de "pregunta primer, decideix després".
Scanner scanner = new Scanner(System.in);
String resposta;
do {
System.out.print("Registrar una altra devolucio? (si/no): ");
resposta = scanner.nextLine().trim();
} while (!resposta.equalsIgnoreCase("si") && !resposta.equalsIgnoreCase("no"));
System.out.println("Has respost: " + resposta);Fixa't que resposta es declara fora del do. Si la declaressis dins del bloc, no existiria a la condició del while ni després del bucle. És un error de compilació molt habitual quan es comença.
- El bucle
for
forfor agrupa els quatre elements del bucle en una sola línia, cosa que fa impossible oblidar l'actualització. És el bucle de referència quan saps quantes vegades cal repetir.
Dissecat terme a terme:
int i = 1— inicialització. S'executa una sola vegada, abans de tot. La variableinomés existeix dins del bucle (fora donaràcannot find symbol), cosa que és un avantatge: no contamina la resta del mètode.i <= 5— condició. S'avalua abans de cada iteració, igual que enwhile. Si és falsa d'entrada, el cos no s'executa mai.i++— actualització. S'executa després del cos, abans de tornar a comprovar la condició.- El cos entre claus.
L'ordre real d'execució, que convé tenir gravat:
flowchart TD
A["int i = 1 (una sola vegada)"] --> B{"i <= 5"}
B -- "true" --> C["Cos"]
C --> D["i++"]
D --> B
B -- "false" --> E["Sortir del bucle"]
Aplicat a BiblioTech, per imprimir l'escala de multes de la primera setmana:
final double TARIFA_DIARIA = 0.25;
System.out.println("DIES MULTA");
for (int dia = 1; dia <= 7; dia++) {
double multa = dia * TARIFA_DIARIA;
System.out.printf("%4d %6.2f EUR%n", dia, multa);
}Variants que veuràs en codi real:
// Compte enrere
for (int dies = 15; dies > 0; dies--) { ... }
// Salts de dos en dos
for (int dia = 0; dia <= 30; dia += 5) { ... }
// Dues variables de control (separades per coma)
for (int i = 0, j = 10; i < j; i++, j--) { ... }
// Totes les seccions son opcionals: aixo es un bucle infinit valid
for (;;) { ... }Un consell de nomenclatura: i, j, k són acceptables per a índexs numèrics purs (és una convenció universal heretada de les matemàtiques), però tan bon punt la variable signifiqui alguna cosa, posa-li nom: dia, intent, numeroLlibre. El codi es llegeix moltes més vegades de les que s'escriu.
- Taula comparativa: quan fer servir cadascun
Els tres bucles són intercanviables des del punt de vista teòric: qualsevol cosa que facis amb un la pots fer amb un altre. L'elecció és una qüestió de comunicar la intenció.
| Criteri | while |
do-while |
for |
|---|---|---|---|
| Condició avaluada | Abans | Després | Abans |
| Iteracions mínimes | 0 | 1 | 0 |
| Saps quantes vegades? | No | No | Sí |
| Parts del bucle | Disperses | Disperses | Juntes a la capçalera |
| Risc d'oblidar l'actualització | Alt | Alt | Baix |
| Ús típic | "Mentre quedi feina" | Menús, validació d'entrada | Recorreguts i repeticions comptades |
| Exemple BiblioTech | Acumular multa fins al sostre | Demanar opció fins que sigui vàlida | Taula de multes del dia 1 al 30 |
Regla pràctica que pots aplicar sense pensar-hi gaire:
- Repetir un nombre conegut de vegades o recórrer índexs? →
for. - Repetir mentre es compleixi alguna cosa, potser zero vegades? →
while. - Fer-ho almenys una vegada i després decidir? →
do-while.
- El bucle
for-each: existeix, però arriba més tard
for-each: existeix, però arriba més tardJava té un quart bucle, el for millorat o for-each, amb aquesta sintaxi:
Es llegeix com "per a cada titol de cataleg". És més curt i elimina tota l'aritmètica d'índexs, així que és el bucle preferit quan hi és aplicable. La seva limitació és que només funciona sobre arrays i col·leccions, i cap de les dues coses existeix encara a la teva caixa d'eines: els arrays arriben a la lliçó 05-01 i les col·leccions a la resta del mòdul 5.
Per això aquí només el deixem anotat. Quan arribis al mòdul 5 i tinguis una llista real de llibres, el for-each serà el teu bucle per defecte. Fins llavors, while, do-while i for cobreixen tot el que necessites.
- Bucles imbricats: la taula de multes de BiblioTech
Un bucle imbricat és un bucle dins del cos d'un altre. El bucle intern completa totes les seves iteracions per cada iteració de l'extern. Si l'extern fa 4 voltes i l'intern 5, el cos intern s'executa 4 × 5 = 20 vegades.
El cas clàssic és generar una taula de dues dimensions. Nexus Software vol una matriu de referència amb la multa segons els dies de retard (files) i el nombre de llibres retornats alhora (columnes):
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
final double TARIFA_DIARIA = 0.25;
final double MULTA_MAXIMA = 20.0;
System.out.println("TAULA DE MULTES DE BIBLIOTECH (EUR)");
System.out.println("Files: dies de retard | Columnes: llibres retornats");
System.out.println();
// Capcalera de columnes
System.out.printf("%8s", "DIES\\LLI");
for (int llibres = 1; llibres <= 4; llibres++) {
System.out.printf("%9d", llibres);
}
System.out.println();
// Bucle EXTERN: recorre els dies de retard de 5 en 5
for (int dies = 5; dies <= 25; dies += 5) {
System.out.printf("%8d", dies);
// Bucle INTERN: per cada fila, recorre les 4 columnes
for (int llibres = 1; llibres <= 4; llibres++) {
double multa = dies * TARIFA_DIARIA * llibres;
if (multa > MULTA_MAXIMA) {
multa = MULTA_MAXIMA; // sostre per devolucio
}
System.out.printf("%9.2f", multa);
}
System.out.println(); // salt de linia en acabar la fila
}
}
}Sortida:
TAULA DE MULTES DE BIBLIOTECH (EUR)
Files: dies de retard | Columnes: llibres retornats
DIES\LLI 1 2 3 4
5 1,25 2,50 3,75 5,00
10 2,50 5,00 7,50 10,00
15 3,75 7,50 11,25 15,00
20 5,00 10,00 15,00 20,00
25 6,25 12,50 18,75 20,00Tres detalls importants:
- Cada bucle té la seva pròpia variable de control (
diesillibres). Reutilitzar la mateixa en tots dos és un error garantit. - El
System.out.println()del final del bucle extern és fora de l'intern: s'executa una vegada per fila, no una vegada per cel·la. Si el posessis a dins, tindries una columna vertical en lloc d'una taula. - El sostre de
MULTA_MAXIMAs'aplica dins del bucle intern, cel·la a cel·la, i es nota a la cantonada inferior dreta, on 25 dies × 4 llibres donaria 25,00 € però queda en 20,00 €.
Compte amb l'explosió combinatòria: dos bucles imbricats de 1.000 iteracions cadascun són un milió d'execucions del cos. Tres nivells, mil milions. La imbricació és legítima, però cada nivell multiplica el cost.
- Patrons fonamentals: comptador, acumulador i cerca amb bandera
Gairebé tot el que faràs amb bucles és una combinació de tres patrons. Val la pena aprendre'ls amb nom, perquè els reconeixeràs en tot el codi professional que llegeixis.
8.1 Comptador
Una variable entera que s'incrementa quan passa alguna cosa. Serveix per respondre "quants?".
int devolucionsAmbRetard = 0;
for (int dies = 10; dies <= 30; dies += 5) {
if (dies > 15) { // DIES_PRESTEC
devolucionsAmbRetard++; // el comptador nomes puja si es compleix la condicio
}
}
System.out.println("Devolucions fora de termini: " + devolucionsAmbRetard); // 38.2 Acumulador
Una variable que va sumant valors. Serveix per respondre "quant en total?". S'inicialitza sempre fora del bucle i a zero (o a "" per a text, encara que això té matisos que veurem a l'apartat 12).
final double TARIFA_DIARIA = 0.25;
double totalMultes = 0.0;
for (int dies = 5; dies <= 25; dies += 5) {
double multa = dies * TARIFA_DIARIA;
totalMultes += multa; // acumulador
System.out.printf(" %2d dies -> %.2f EUR%n", dies, multa);
}
System.out.printf("TOTAL: %.2f EUR%n", totalMultes); // 18,75 EURL'error clàssic de l'acumulador és inicialitzar-lo dins del bucle. Si escrius double totalMultes = 0.0; com a primera línia del cos, es reinicia a cada volta i al final val el mateix que l'últim element. És un error tan freqüent que la lliçó 02-05 el fa servir com a cas pràctic de depuració.
8.3 Cerca amb bandera booleana
Una variable boolean que comença en false i es posa a true quan es troba el que es buscava. Serveix per respondre "n'hi ha algun que…?".
final int LLINDAR_LLEU = 7;
boolean hiHaRetardGreu = false; // bandera, inicialitzada a false
for (int dies = 1; dies <= 6; dies++) {
if (dies > LLINDAR_LLEU) {
hiHaRetardGreu = true; // s'aixeca la bandera
}
}
if (hiHaRetardGreu) {
System.out.println("Hi ha almenys una devolucio amb retard greu.");
} else {
System.out.println("Cap devolucio no supera el llindar lleu.");
}Un cop aixecada, la bandera no es torna a abaixar: això és el que la fa fiable. I tan bon punt s'aixeca, continuar iterant sol ser innecessari; la manera de tallar el bucle en aquest punt és break, que s'estudia en detall a la lliçó 02-04.
8.4 Els tres patrons junts: una tanda de devolucions
Aquest és l'exemple que resumeix el mòdul. La Marta Ruiz arriba al taulell amb diversos llibres i el bibliotecari els processa un a un:
package com.nexussoftware.bibliotech;
import java.util.Scanner;
public class BiblioTechApp {
public static void main(String[] args) {
final int DIES_PRESTEC = 15;
final double TARIFA_DIARIA = 0.25;
final double MULTA_MAXIMA = 20.0;
final int LLINDAR_LLEU = 7;
Scanner scanner = new Scanner(System.in);
System.out.print("Empleat: ");
String empleat = scanner.nextLine().trim();
System.out.print("Quants llibres retorna? ");
int nombreLlibres = Integer.parseInt(scanner.nextLine().trim());
// --- Variables d'acumulacio de la tanda ---
int llibresAmbRetard = 0; // comptador
double totalMultes = 0.0; // acumulador
int retardMesGran = 0; // maxim
String llibreMesRetardat = "-"; // dada associada al maxim
boolean sostreAssolit = false; // bandera
System.out.println();
for (int llibre = 1; llibre <= nombreLlibres; llibre++) {
System.out.printf("--- Llibre %d de %d ---%n", llibre, nombreLlibres);
System.out.print(" Titol: ");
String titol = scanner.nextLine().trim();
System.out.print(" Dies transcorreguts: ");
int diesTranscorreguts = Integer.parseInt(scanner.nextLine().trim());
int diesRetard = diesTranscorreguts - DIES_PRESTEC;
if (diesRetard < 0) {
diesRetard = 0;
}
double multa = diesRetard * TARIFA_DIARIA;
if (multa > MULTA_MAXIMA) {
multa = MULTA_MAXIMA;
sostreAssolit = true; // bandera
}
if (diesRetard > 0) {
llibresAmbRetard++; // comptador
}
totalMultes += multa; // acumulador
if (diesRetard > retardMesGran) { // maxim + dada associada
retardMesGran = diesRetard;
llibreMesRetardat = titol;
}
String gravetat;
if (diesRetard == 0) {
gravetat = "SENSE RETARD";
} else if (diesRetard <= LLINDAR_LLEU) {
gravetat = "LLEU";
} else {
gravetat = "GREU";
}
System.out.printf(" -> %s | retard %d dies | multa %.2f EUR%n%n",
gravetat, diesRetard, multa);
}
System.out.println("=== RESUM DE LA TANDA ===");
System.out.printf("%-24s %s%n", "Empleat:", empleat);
System.out.printf("%-24s %d%n", "Llibres retornats:", nombreLlibres);
System.out.printf("%-24s %d%n", "Amb retard:", llibresAmbRetard);
System.out.printf("%-24s %.2f EUR%n", "Total a pagar:", totalMultes);
System.out.printf("%-24s %s (%d dies)%n", "Retard mes gran:",
llibreMesRetardat, retardMesGran);
if (sostreAssolit) {
System.out.println("AVIS: algun llibre ha assolit la multa maxima.");
}
scanner.close();
}
}Exemple de sessió amb la Marta Ruiz i tres llibres:
Empleat: Marta Ruiz Quants llibres retorna? 3 --- Llibre 1 de 3 --- Titol: Java Eficac Dies transcorreguts: 27 -> GREU | retard 12 dies | multa 3,00 EUR --- Llibre 2 de 3 --- Titol: Patrons de Disseny Dies transcorreguts: 12 -> SENSE RETARD | retard 0 dies | multa 0,00 EUR --- Llibre 3 de 3 --- Titol: Refactoritzacio Dies transcorreguts: 120 -> GREU | retard 105 dies | multa 20,00 EUR === RESUM DE LA TANDA === Empleat: Marta Ruiz Llibres retornats: 3 Amb retard: 2 Total a pagar: 23,00 EUR Retard mes gran: Refactoritzacio (105 dies) AVIS: algun llibre ha assolit la multa maxima.
Compara això amb el final del mòdul 1, on processar dos llibres exigia duplicar trenta línies i trenta variables numerades. Ara processa tres, deu o mil llibres amb el mateix codi.
I aquí apareix la limitació que has de notar conscientment. En acabar el bucle, què saps dels llibres processats? Només el que has anat acumulant en variables escalars: quants, quant sumen, quin va ser el pitjor. Les dades individuals s'han perdut: el títol del llibre 1 es va sobreescriure amb el del llibre 2. No pots imprimir el detall al final, ni ordenar els llibres per multa, ni cercar-ne un de concret. Per a això calen variables que guardin molts valors alhora, i això són els arrays (lliçó 05-01) i les col·leccions (resta del mòdul 5). Guarda aquesta sensació: és exactament la que justifica el mòdul 5.
- Bucle de validació d'entrada
Aquest és el patró que resol la frustració amb què obria la lliçó. En lloc d'acabar el programa quan la dada és invàlida, es torna a demanar.
L'estructura canònica és un do-while, perquè sempre cal preguntar almenys una vegada:
Scanner scanner = new Scanner(System.in);
int diesTranscorreguts;
boolean entradaValida;
do {
System.out.print("Dies transcorreguts (0-3650): ");
String entrada = scanner.nextLine().trim();
diesTranscorreguts = Integer.parseInt(entrada);
entradaValida = diesTranscorreguts >= 0 && diesTranscorreguts <= 3650;
if (!entradaValida) {
System.out.println(" Valor fora de rang. Torna-ho a provar.");
}
} while (!entradaValida);
System.out.println("Acceptat: " + diesTranscorreguts + " dies.");Sessió d'exemple:
Dies transcorreguts (0-3650): -5 Valor fora de rang. Torna-ho a provar. Dies transcorreguts (0-3650): 99999 Valor fora de rang. Torna-ho a provar. Dies transcorreguts (0-3650): 27 Acceptat: 27 dies.
El mateix patró val per a text:
String empleat;
do {
System.out.print("Empleat: ");
empleat = scanner.nextLine().trim();
if (empleat.isBlank()) {
System.out.println(" El nom no pot estar buit.");
}
} while (empleat.isBlank());I una variant prudent per a producció: limitar el nombre d'intents, perquè el programa no quedi a mercè d'un usuari que s'equivoca eternament (o d'un script automàtic mal fet):
final int MAX_INTENTS = 3;
int intents = 0;
boolean valid = false;
int dies = 0;
while (!valid && intents < MAX_INTENTS) {
intents++;
System.out.printf("Dies transcorreguts (intent %d de %d): ", intents, MAX_INTENTS);
dies = Integer.parseInt(scanner.nextLine().trim());
valid = dies >= 0;
if (!valid) {
System.out.println(" Els dies no poden ser negatius.");
}
}
if (!valid) {
System.err.println("Massa intents fallits. Operacio cancellada.");
} else {
System.out.println("Acceptat: " + dies);
}Continua vigent l'advertència de la lliçó anterior: si l'usuari escriu vint, Integer.parseInt atura el programa i cap bucle no el salva. Aquest forat es tanca al mòdul 6.
- Bucle de menú (avançament del projecte)
El patró que donarà forma al projecte final del mòdul (lliçó 02-06) és un bucle que mostra opcions, llegeix l'elecció i actua, fins que l'usuari decideix sortir.
Scanner scanner = new Scanner(System.in);
boolean sortir = false;
while (!sortir) {
System.out.println();
System.out.println("=== BIBLIOTECH - Nexus Software ===");
System.out.println(" 1. Registrar devolucio");
System.out.println(" 2. Simular prestec");
System.out.println(" 0. Sortir");
System.out.print("Opcio: ");
String opcio = scanner.nextLine().trim();
if (opcio.equals("1")) {
System.out.println("[Registrant devolucio...]");
} else if (opcio.equals("2")) {
System.out.println("[Simulant prestec...]");
} else if (opcio.equals("0")) {
System.out.println("Fins aviat.");
sortir = true; // la bandera talla el bucle
} else {
System.out.println("Opcio no reconeguda.");
}
}
scanner.close();Aquí el bucle no compta iteracions: es repeteix mentre l'usuari no digui que se'n va. La bandera sortir controla la condició. És l'aplicació directa del patró de bandera de l'apartat 8.3.
A la lliçó 02-03 substituiràs aquesta escala d'if-else per un switch, molt més llegible per despatxar opcions, i a la 02-06 muntaràs el programa complet.
- Bucles infinits: intencionats i accidentals
Un bucle infinit és aquell la condició del qual no esdevé mai falsa. N'hi ha dues categories radicalment diferents.
Intencionats. S'escriuen a propòsit i s'abandonen des de dins amb break o return. Són idiomàtics en menús, servidors i bucles d'esdeveniments:
while (true) {
String opcio = scanner.nextLine().trim();
if (opcio.equals("0")) {
break; // unica sortida, explicita
}
System.out.println("Has triat: " + opcio);
}while (true) i for (;;) són equivalents; el primer es llegeix millor. La condició perquè un bucle infinit sigui legítim és que la sortida sigui evident en llegir el cos.
Accidentals. Són errors. Les tres causes, per freqüència:
// 1. Falta l'actualitzacio
int i = 0;
while (i < 10) {
System.out.println(i);
// falta i++! -> imprimeix 0 per sempre
}
// 2. L'actualitzacio allunya de la condicio
for (int dies = 15; dies > 0; dies++) { // hauria de ser dies--
System.out.println(dies);
}
// 3. La condicio depen d'alguna cosa que el cos no canvia
boolean seguir = true;
while (seguir) {
System.out.println("Processant...");
// ningu no posa 'seguir' a false
}Com sortir d'un bucle infinit penjat al terminal: prem Ctrl + C. Això envia un senyal d'interrupció al procés Java i el termina. Si l'has llançat des de l'IDE, hi ha un botó quadrat vermell de "Stop" a la consola. I si el bucle està imprimint milions de línies i el terminal no respon, tanca la finestra; no hauràs perdut res, perquè el programa encara no desa estat.
Com evitar-los, en ordre d'utilitat:
- En escriure un
while, escriu primer l'actualització i després la resta del cos. - Abans d'executar, pregunta't: quina línia concreta fa que aquesta condició acabi essent falsa? Si no la saps assenyalar, tens un bucle infinit.
- Durant el desenvolupament, afegeix un límit de seguretat:
if (iteracions++ > 1_000_000) break;.
- Cost de les iteracions i el parany de concatenar
String
StringCada iteració costa temps. Un cos que triga 1 mil·lisegon, repetit un milió de vegades, són 16 minuts. Dues conseqüències pràctiques:
Treu del bucle tot el que no canvia. Aquest càlcul es repeteix inútilment:
// MALAMENT: la multiplicacio de les constants es recalcula a cada volta
for (int dia = 1; dia <= 10_000; dia++) {
double limit = TARIFA_DIARIA * 30 * 12; // sempre el mateix valor
...
}
// BE: es calcula una vegada
double limit = TARIFA_DIARIA * 30 * 12;
for (int dia = 1; dia <= 10_000; dia++) {
...
}No concatenis String amb + dins de bucles llargs. Recorda del mòdul 1 que String és immutable: cada += no modifica el text, sinó que crea un objecte nou copiant tot el contingut anterior. En un bucle de n iteracions això significa n objectes i un cost que creix amb el quadrat de n.
// MALAMENT: 10.000 objectes String intermedis, cadascun mes llarg que l'anterior
String informe = "";
for (int dia = 1; dia <= 10_000; dia++) {
informe += "Dia " + dia + "\n";
}
// BE: StringBuilder modifica una unica memoria intermedia interna
StringBuilder informe = new StringBuilder();
for (int dia = 1; dia <= 10_000; dia++) {
informe.append("Dia ").append(dia).append("\n");
}
System.out.println(informe.toString());StringBuilder és una classe de la biblioteca estàndard pensada exactament per a això: append afegeix al final sense copiar-ho tot, i toString() produeix el String final una sola vegada. Per a bucles de tres o quatre voltes la diferència és irrellevant i la concatenació normal és més llegible; a partir d'uns quants centenars d'iteracions, StringBuilder és obligatori. En un cas real mesurat, construir 100.000 línies passa de desenes de segons amb += a uns pocs mil·lisegons amb StringBuilder.
Una alternativa quan l'única cosa que vols és mostrar el text: imprimir dins del bucle en lloc d'acumular-lo. System.out.println dins d'un bucle no té aquest problema (encara que tampoc no és gratuït: la sortida per consola és de les operacions més lentes que existeixen).
Errors Habituals i Consells
1. Oblidar l'actualització. La causa número u de bucles infinits. En for és gairebé impossible; en while és el risc permanent.
2. Punt i coma després de la capçalera.
for (int i = 0; i < 5; i++); // <-- bucle buit
{
System.out.println(i); // ni tan sols compila: 'i' ja no existeix
}3. Error per un (off-by-one). L'error més freqüent de la programació. < i <= no són intercanviables:
for (int i = 1; i < 5; i++) // 1, 2, 3, 4 -> QUATRE voltes
for (int i = 1; i <= 5; i++) // 1, 2, 3, 4, 5 -> CINC voltes
for (int i = 0; i < 5; i++) // 0, 1, 2, 3, 4 -> CINC voltesQuan dubtis, escriu la traça de les tres primeres i l'última iteració en un paper.
4. Inicialitzar l'acumulador dins del bucle. El resultat final acaba essent el de l'últim element en lloc de la suma. Inicialitza sempre abans del for/while.
5. Declarar dins del do una variable que necessites a la condició. No compila: l'àmbit del bloc acaba a la clau de tancament.
6. Modificar la variable de control dins del cos d'un for. Compila, però converteix el bucle en un trencaclosques:
for (int i = 0; i < 10; i++) {
if (condicio) { i += 3; } // legal, pero ningu no ho espera en llegir la capcalera
}Si necessites saltar iteracions, fes servir continue (lliçó 02-04) o replanteja el bucle.
7. Comparar double a la condició del bucle. for (double d = 0.0; d != 1.0; d += 0.1) no acaba mai, perquè 0.1 no és representable exactament en binari i la suma mai no val 1.0 exacte. Fes servir < en lloc de !=, o compta amb enters i divideix.
8. Consell: anomena les variables de control pel que signifiquen. for (int intent = 1; intent <= MAX_INTENTS; intent++) s'entén sense comentaris; for (int i = 1; i <= n; i++) obliga a mirar el cos.
9. Consell: mantén els cossos curts. Si el cos del teu bucle ocupa cinquanta línies, costa veure on és l'actualització i què s'acumula. Quan aprenguis mètodes (lliçó 03-03), extreure el cos serà la solució natural.
Exercicis
Exercici 1: escala de multes amb for
Escriu un programa que imprimeixi l'escala oficial de multes de BiblioTech des d'1 fins a 30 dies de retard, de cinc en cinc, amb tres columnes: dies, multa sense sostre i multa aplicada (amb MULTA_MAXIMA). Afegeix-hi una columna final amb l'etiqueta de gravetat (LLEU si els dies són <= LLINDAR_LLEU, GREU en cas contrari). En acabar, imprimeix quantes de les files mostrades superaven el sostre, fent servir un comptador.
Fes servir printf per alinear la taula i les constants TARIFA_DIARIA = 0.25, MULTA_MAXIMA = 20.0 i LLINDAR_LLEU = 7.
Exercici 2: validació robusta amb do-while
Escriu un programa que demani a l'usuari el nom d'un empleat i els seus dies transcorreguts, repetint cada pregunta fins a obtenir un valor vàlid:
- El nom no pot estar en blanc ni tenir menys de 3 caràcters.
- Els dies transcorreguts han d'estar entre 0 i 3650.
Quan totes dues dades siguin vàlides, calcula el retard saturat a zero i la multa amb sostre, i imprimeix el rebut. Mostra a la sortida quants intents fallits hi va haver en total (un comptador que s'incrementi a cada rebuig).
Exercici 3: acumulació d'una tanda amb bucles imbricats
Nexus Software vol un informe de la sessió d'un bibliotecari. El programa ha de:
- Preguntar quants empleats ha atès.
- Per cada empleat, preguntar el seu nom i quants llibres retorna.
- Per cada llibre, preguntar els dies transcorreguts i calcular-ne la multa amb sostre.
- Mostrar, en acabar cada empleat, el seu subtotal de multes i quants llibres portava amb retard.
- Mostrar, al final de tot, el total general de multes, el nombre total de llibres processats i el nom de l'empleat amb més multa acumulada.
Fes servir un bucle extern per als empleats i un d'intern per als seus llibres. Després de resoldre'l, respon per escrit: què t'hauria calgut per poder imprimir al final el detall llibre a llibre de tota la sessió?
Solucions
Solució 1
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
final double TARIFA_DIARIA = 0.25;
final double MULTA_MAXIMA = 20.0;
final int LLINDAR_LLEU = 7;
int filesAmbSostre = 0; // comptador
System.out.println("ESCALA OFICIAL DE MULTES - BiblioTech");
System.out.printf("%6s %12s %12s %s%n", "DIES", "SENSE SOSTRE", "APLICADA", "GRAVETAT");
System.out.println("-".repeat(48));
// Inicialitzacio: dies = 1. Condicio: <= 30. Actualitzacio: +5.
// Recorre 1, 6, 11, 16, 21, 26 -> sis files.
for (int dies = 1; dies <= 30; dies += 5) {
double multaSenseSostre = dies * TARIFA_DIARIA;
double multaAplicada = multaSenseSostre;
if (multaAplicada > MULTA_MAXIMA) {
multaAplicada = MULTA_MAXIMA;
filesAmbSostre++; // el comptador nomes puja en aplicar el sostre
}
String gravetat;
if (dies <= LLINDAR_LLEU) {
gravetat = "LLEU";
} else {
gravetat = "GREU";
}
System.out.printf("%6d %12.2f %12.2f %s%n",
dies, multaSenseSostre, multaAplicada, gravetat);
}
System.out.println("-".repeat(48));
System.out.println("Files que han assolit el sostre: " + filesAmbSostre);
}
}Sortida:
ESCALA OFICIAL DE MULTES - BiblioTech
DIES SENSE SOSTRE APLICADA GRAVETAT
------------------------------------------------
1 0,25 0,25 LLEU
6 1,50 1,50 LLEU
11 2,75 2,75 GREU
16 4,00 4,00 GREU
21 5,25 5,25 GREU
26 6,50 6,50 GREU
------------------------------------------------
Files que han assolit el sostre: 0Observació interessant: amb la tarifa actual, cap fila fins a 30 dies no arriba al sostre de 20 €, perquè caldrien 80 dies de retard. El comptador val 0 i això és correcte, no un error. Comprova que el programa es comporta com esperes canviant el límit del bucle a dies <= 120.
Solució 2
package com.nexussoftware.bibliotech;
import java.util.Scanner;
public class BiblioTechApp {
public static void main(String[] args) {
final int DIES_PRESTEC = 15;
final double TARIFA_DIARIA = 0.25;
final double MULTA_MAXIMA = 20.0;
Scanner scanner = new Scanner(System.in);
int intentsFallits = 0;
// --- Bucle de validacio del nom ---
String empleat;
do {
System.out.print("Empleat: ");
empleat = scanner.nextLine().trim();
if (empleat.length() < 3) {
System.out.println(" El nom ha de tenir almenys 3 caracters.");
intentsFallits++;
}
} while (empleat.length() < 3);
// --- Bucle de validacio dels dies ---
int diesTranscorreguts;
boolean diesValids;
do {
System.out.print("Dies transcorreguts (0-3650): ");
diesTranscorreguts = Integer.parseInt(scanner.nextLine().trim());
diesValids = diesTranscorreguts >= 0 && diesTranscorreguts <= 3650;
if (!diesValids) {
System.out.println(" Valor fora del rang permes.");
intentsFallits++;
}
} while (!diesValids);
// --- Calcul, ja amb dades garantides valides ---
int diesRetard = diesTranscorreguts - DIES_PRESTEC;
if (diesRetard < 0) {
diesRetard = 0;
}
double multa = diesRetard * TARIFA_DIARIA;
if (multa > MULTA_MAXIMA) {
multa = MULTA_MAXIMA;
}
System.out.println();
System.out.println("=== REBUT DE DEVOLUCIO ===");
System.out.printf("%-20s %s%n", "Empleat:", empleat);
System.out.printf("%-20s %d%n", "Dies transcorreguts:", diesTranscorreguts);
System.out.printf("%-20s %d%n", "Dies de retard:", diesRetard);
System.out.printf("%-20s %.2f EUR%n", "Multa:", multa);
System.out.printf("%-20s %d%n", "Intents fallits:", intentsFallits);
scanner.close();
}
}Sessió d'exemple:
Empleat: El nom ha de tenir almenys 3 caracters. Empleat: DA El nom ha de tenir almenys 3 caracters. Empleat: Diego Alonso Dies transcorreguts (0-3650): -3 Valor fora del rang permes. Dies transcorreguts (0-3650): 27 === REBUT DE DEVOLUCIO === Empleat: Diego Alonso Dies transcorreguts: 27 Dies de retard: 12 Multa: 3,00 EUR Intents fallits: 3
Dues decisions de disseny que convé raonar:
empleatidiesTranscorregutses declaren fora deldo, perquè es fan servir a la condició delwhilei després del bucle.- Al segon bucle es fa servir la bandera
diesValidsen lloc de repetir la condició complexa alwhile. Repetir-la funcionaria, però duplicar una condició és duplicar un lloc on equivocar-se en canviar-la.
Solució 3
package com.nexussoftware.bibliotech;
import java.util.Scanner;
public class BiblioTechApp {
public static void main(String[] args) {
final int DIES_PRESTEC = 15;
final double TARIFA_DIARIA = 0.25;
final double MULTA_MAXIMA = 20.0;
Scanner scanner = new Scanner(System.in);
System.out.print("Quants empleats has ates? ");
int nombreEmpleats = Integer.parseInt(scanner.nextLine().trim());
// Acumuladors globals de la sessio
double totalGeneral = 0.0;
int totalLlibres = 0;
double multaMesGranEmpleat = 0.0;
String empleatAmbMesMulta = "-";
// ===== BUCLE EXTERN: empleats =====
for (int e = 1; e <= nombreEmpleats; e++) {
System.out.printf("%n===== Empleat %d de %d =====%n", e, nombreEmpleats);
System.out.print("Nom: ");
String empleat = scanner.nextLine().trim();
System.out.print("Llibres que retorna: ");
int nombreLlibres = Integer.parseInt(scanner.nextLine().trim());
// Acumuladors LOCALS de l'empleat: es reinicien a cada volta
// del bucle extern, que es justament el que volem aqui.
double subtotal = 0.0;
int llibresAmbRetard = 0;
// ===== BUCLE INTERN: llibres d'AQUEST empleat =====
for (int l = 1; l <= nombreLlibres; l++) {
System.out.printf(" Llibre %d - dies transcorreguts: ", l);
int diesTranscorreguts = Integer.parseInt(scanner.nextLine().trim());
int diesRetard = diesTranscorreguts - DIES_PRESTEC;
if (diesRetard < 0) {
diesRetard = 0;
}
double multa = diesRetard * TARIFA_DIARIA;
if (multa > MULTA_MAXIMA) {
multa = MULTA_MAXIMA;
}
if (diesRetard > 0) {
llibresAmbRetard++;
}
subtotal += multa;
totalLlibres++; // acumulador global
System.out.printf(" retard %d dies -> %.2f EUR%n", diesRetard, multa);
}
totalGeneral += subtotal;
if (subtotal > multaMesGranEmpleat) {
multaMesGranEmpleat = subtotal;
empleatAmbMesMulta = empleat;
}
System.out.printf(" SUBTOTAL %s: %.2f EUR (%d llibres amb retard de %d)%n",
empleat, subtotal, llibresAmbRetard, nombreLlibres);
}
System.out.println();
System.out.println("========== INFORME DE LA SESSIO ==========");
System.out.printf("%-26s %d%n", "Empleats atesos:", nombreEmpleats);
System.out.printf("%-26s %d%n", "Llibres processats:", totalLlibres);
System.out.printf("%-26s %.2f EUR%n", "Total recaptat:", totalGeneral);
System.out.printf("%-26s %s (%.2f EUR)%n", "Multa mes gran:",
empleatAmbMesMulta, multaMesGranEmpleat);
System.out.println("=========================================");
scanner.close();
}
}Sessió abreujada amb dos empleats:
Quants empleats has ates? 2
===== Empleat 1 de 2 =====
Nom: Marta Ruiz
Llibres que retorna: 2
Llibre 1 - dies transcorreguts: 27
retard 12 dies -> 3,00 EUR
Llibre 2 - dies transcorreguts: 10
retard 0 dies -> 0,00 EUR
SUBTOTAL Marta Ruiz: 3,00 EUR (1 llibres amb retard de 2)
===== Empleat 2 de 2 =====
Nom: Nuria Vidal
Llibres que retorna: 1
Llibre 1 - dies transcorreguts: 120
retard 105 dies -> 20,00 EUR
SUBTOTAL Nuria Vidal: 20,00 EUR (1 llibres amb retard de 1)
========== INFORME DE LA SESSIO ==========
Empleats atesos: 2
Llibres processats: 3
Total recaptat: 23,00 EUR
Multa mes gran: Nuria Vidal (20,00 EUR)
=========================================El punt clau de l'exercici és la distinció entre acumuladors globals i locals. totalGeneral i totalLlibres es declaren abans del bucle extern i sobreviuen a tota la sessió. subtotal i llibresAmbRetard es declaren dins del bucle extern, de manera que es creen i es posen a zero a cada empleat. Col·locar malament qualsevol d'aquestes declaracions produeix un informe silenciosament incorrecte: si subtotal fos a fora, arrossegaria les multes de l'empleat anterior.
Resposta a la pregunta final: per imprimir al final el detall llibre a llibre necessitaries desar totes les dades llegides, no només els seus agregats. Amb variables escalars és impossible: cada titol i cada multa se sobreescriuen a la iteració següent. Caldrien estructures capaces de contenir molts valors sota un sol nom —un array (lliçó 05-01) o un ArrayList (lliçó 05-03)— i, per agrupar títol, ISBN i multa com una unitat, una classe pròpia Llibre (mòdul 3). Aquest és exactament el camí que segueix el curs.
Conclusió
El teu programa ja repeteix. Has dissecat els tres bucles clàssics: while, que comprova abans i pot no executar-se mai; do-while, que executa almenys una vegada i és l'eina natural de la validació i els menús; i for, que agrupa inicialització, condició i actualització en una capçalera i és el bucle de les repeticions comptades. Saps que existeix el for-each i per què esperaràs al mòdul 5 per fer-lo servir. Has imbricat bucles per generar la taula de multes de BiblioTech, i sobretot has après els quatre patrons que resolen gairebé tot: comptador, acumulador, cerca amb bandera i bucle de validació, més el bucle de menú que estructurarà el projecte del mòdul. I coneixes els dos perills permanents: el bucle infinit accidental i la concatenació de String dins de bucles llargs.
BiblioTechApp processa ara tandes senceres de devolucions, insisteix fins que l'usuari introdueix dades vàlides i produeix un resum agregat de la sessió. Però també n'has vist el sostre: en acabar el bucle només queden els agregats; les dades individuals s'han perdut perquè no tens on desar-les. Aquesta mancança es resoldrà al mòdul 5.
A la lliçó següent, Sentències switch, substituiràs l'escala d'if-else del despatxador de menú per una construcció dissenyada específicament per triar entre molts valors d'una mateixa variable. Veuràs el switch clàssic amb el seu temible fall-through, el switch amb fletxes de Java 14 endavant i el switch com a expressió que retorna un valor, i amb ells escriuràs la primera versió del despatxador d'opcions de BiblioTech.
Curs de Programació en Java
Mòdul 1: Introducció a Java
- Introducció a Java
- Configuració de l'entorn de desenvolupament
- Sintaxi i estructura bàsica
- Variables i tipus de dades
- Operadors
- Entrada i sortida per consola
- El teu primer programa complet: BiblioTech
Mòdul 2: Flux de control
- Sentències condicionals
- Bucles
- Sentències switch
- Break i continue
- Depuració i traces d'execució
- Projecte: menú interactiu de BiblioTech
Mòdul 3: Programació orientada a objectes
- Introducció a la POO
- Classes i objectes
- Mètodes
- Constructors
- Herència
- Polimorfisme
- Encapsulament
- Abstracció
- La classe Object: equals, hashCode i toString
Mòdul 4: Programació orientada a objectes avançada
- Interfícies
- Classes abstractes
- Classes internes
- Classes anònimes
- Expressions lambda
- Interfícies funcionals i referències a mètodes
- Enumeracions i registres
Mòdul 5: Estructures de dades i col·leccions
- Arrays
- El framework de col·leccions
- ArrayList
- LinkedList
- HashMap
- HashSet
- Cua i Deque
- Pila
- Ordenació i cerca en col·leccions
Mòdul 6: Gestió d'excepcions
- Introducció a les excepcions
- Bloc try-catch
- Throw i throws
- Excepcions personalitzades
- Bloc finally
- Try-with-resources i AutoCloseable
- Estratègies de gestió d'errors i logging
Mòdul 7: Entrada/sortida de fitxers
- Lectura de fitxers
- Escriptura de fitxers
- Fluxos de fitxers
- BufferedReader i BufferedWriter
- Serialització
- L'API NIO.2: Path i Files
- Formats d'intercanvi: CSV i Properties
Mòdul 8: Multifil i concurrència
- Introducció al multifil
- Creació de fils
- Cicle de vida d'un fil
- Sincronització
- Utilitats de concurrència
- Col·leccions concurrents i variables atòmiques
- Tasques asíncrones amb CompletableFuture
Mòdul 9: Xarxes
- Introducció a les xarxes
- Sockets
- ServerSocket
- DatagramSocket i DatagramPacket
- URL i HttpURLConnection
- El client HTTP modern
Mòdul 10: Temes avançats
- Genèrics
- Anotacions
- Reflexió
- Característiques de Java 8: Streams i Optional
- Dates i hores amb java.time
- Java 9 i més enllà
- Memòria, recol·lecció de brossa i rendiment
Mòdul 11: Frameworks i llibreries de Java
- Introducció als frameworks de Java
- Spring Framework
- Hibernate
- JUnit
- Maven
- Proves avançades amb Mockito
- Llibreries essencials de l'ecosistema
