A la lliçó anterior vas seguir el viatge d'una excepció des del punt en què es llança fins que arriba a la JVM sense que ningú l'atengui: el programa imprimeix un stack trace i mor. Ara interceptaràs aquest viatge. El bloc try-catch és el mecanisme amb què un mètode diu: «això que faré pot fallar, i si falla d'aquesta manera concreta, jo sé què fer».

És la construcció més usada del mòdul i també la més maltractada. Escriure try { ... } catch (Exception e) { e.printStackTrace(); } és tan fàcil que s'ha convertit en un reflex automàtic, i és gairebé sempre la pitjor decisió possible: captura massa, no arregla res i deixa el programa corrent amb l'estat trencat. Aquesta lliçó et dona la versió correcta: què es protegeix exactament dins d'un try, quin codi se salta quan salta l'excepció, com interrogar l'objecte capturat, per què l'ordre de diversos catch no és una qüestió d'estil sinó un requisit del compilador, i —el més important— què es pot fer de debò dins d'un catch més enllà d'imprimir l'error.

Al final tindràs la primera victòria tangible del mòdul: el menú interactiu que vas escriure a 02-06 deixarà de morir-se quan Marta Ruiz escrigui "dotze" on el programa esperava 12.

Contingut

  1. Sintaxi i semàntica del try-catch
  2. Què es protegeix i què se salta
  3. L'objecte excepció i els seus mètodes
  4. Múltiples blocs catch i la regla d'ordre
  5. Multi-catch amb |
  6. Blocs try imbricats
  7. Abast de les variables declarades dins del try
  8. Què es pot fer dins d'un catch
  9. Estratègia 1: recuperar-se amb un valor per defecte
  10. Estratègia 2: reintentar
  11. Estratègia 3: traduir a una altra excepció
  12. Estratègia 4: registrar i rellançar
  13. Antipatrons, amb demostració
  14. El cost real d'una excepció enfront d'un if
  15. BiblioTech: el menú que ja no es trenca
  16. Errors Comuns i Consells
  17. Exercicis

  1. Sintaxi i semàntica del try-catch

La forma mínima:

try {
    // Codi PROTEGIT: pot llancar excepcions
} catch (TipusDExcepcio e) {
    // GESTOR: s'executa nomes si el try ha llancat una excepcio
    // compatible amb TipusDExcepcio
}

Tres regles de sintaxi que el compilador imposa:

  1. Les claus són obligatòries, fins i tot amb una sola sentència. A diferència d'if o for, aquí no hi ha versió sense claus.
  2. Un try no pot anar sol. Ha de portar almenys un catch, o un finally (06-05), o ser un try-with-resources (06-06). try { ... } a seques no compila.
  3. El paràmetre del catch es declara com el d'un mètode: tipus i nom. Per convenció s'anomena e, ex o alguna cosa descriptiva com fallada o noTrobat.

Un exemple complet amb BiblioTech:

package com.nexussoftware.bibliotech.presentacio;

public class PrimerTryCatch {

    public static void main(String[] args) {
        String entrada = "dotze";                      // el que va escriure l'usuari

        System.out.println("1. Abans del try");

        try {
            System.out.println("2. Dins del try, abans de convertir");
            int dies = Integer.parseInt(entrada);      // (*) llanca NumberFormatException
            System.out.println("3. Dies convertits: " + dies);    // NO s'executa
        } catch (NumberFormatException e) {
            System.out.println("4. Capturada: " + e.getMessage());
        }

        System.out.println("5. Despres del try-catch: el programa SEGUEIX VIU");
    }
}

Sortida:

1. Abans del try
2. Dins del try, abans de convertir
4. Capturada: For input string: "dotze"
5. Despres del try-catch: el programa SEGUEIX VIU

Compara això amb el que passava a 06-01 sense try: el programa moria i el missatge 5 no apareixia mai. Ara l'excepció s'ha aturat aquí: no continua pujant per la pila, el fil no mor, i l'execució continua normalment després del bloc.

La semàntica exacta, punt per punt:

  • Si el try acaba sense excepció, tots els catch s'ignoren completament. L'execució segueix a la línia posterior a l'últim bloc.
  • Si el try llança una excepció, l'execució del try s'abandona en aquell instant —les línies següents no s'executen— i la JVM busca entre els catch el primer el tipus del qual sigui compatible amb l'excepció llançada.
  • Si en troba un, executa el seu cos i després continua després del bloc complet. L'excepció queda consumida: deixa de propagar-se.
  • Si no en troba cap de compatible, l'excepció continua pujant per la pila com si el try no existís, buscant gestor al mètode que ha cridat.

Aquest últim punt és crucial i s'oblida sovint: un try-catch que no captura el tipus llançat no fa absolutament res.

try {
    Integer.parseInt("dotze");                  // llanca NumberFormatException
} catch (ArithmeticException e) {               // tipus INCOMPATIBLE
    System.out.println("Mai no arriba aqui");
}
// La NumberFormatException es propaga igualment: el programa mor

  1. Què es protegeix i què se salta

La regla és simple però té conseqüències que convé veure dibuixades: s'abandona tot el que quedi del try a partir del punt de fallada, per profund que estigui.

package com.nexussoftware.bibliotech.presentacio;

import java.util.ArrayList;
import java.util.List;

public class QueEsSalta {

    public static void main(String[] args) {
        List<String> registrades = new ArrayList<>();
        String[] entrades = { "15", "20", "dotze", "30" };

        try {
            for (String entrada : entrades) {
                int dies = Integer.parseInt(entrada);
                registrades.add(entrada + " -> " + dies + " dies");
                System.out.println("Processada: " + entrada);
            }
            System.out.println("TOTES processades");            // no s'executa
        } catch (NumberFormatException e) {
            System.out.println("Fallada a: " + e.getMessage());
        }

        System.out.println("Registrades: " + registrades);
    }
}

Sortida:

Processada: 15
Processada: 20
Fallada a: For input string: "dotze"
Registrades: [15 -> 15 dies, 20 -> 20 dies]

Fixa't en el que ha passat: l'excepció ha saltat dins del bucle, que era dins del try. S'ha abandonat la iteració en curs, s'ha abandonat el bucle sencer, s'ha abandonat la resta del try (inclòs el println("TOTES processades")) i s'ha saltat directament al catch. L'entrada "30", que era perfectament vàlida, no s'ha processat mai.

Això il·lustra una decisió de disseny que hauràs de prendre constantment: on posar el try. Si el que vols és que una dada dolenta no impedeixi processar les altres, el try ha d'estar dins del bucle, no fora:

for (String entrada : entrades) {
    try {
        int dies = Integer.parseInt(entrada);
        registrades.add(entrada + " -> " + dies + " dies");
    } catch (NumberFormatException e) {
        System.out.println("  Descartada entrada invalida: " + entrada);
        // continue implicit: la seguent iteracio segueix normalment
    }
}
// Resultat: [15 -> 15 dies, 20 -> 20 dies, 30 -> 30 dies]
Posició del try Semàntica Quan usar-la
Fora del bucle La primera fallada avorta tot el procés Tot o res: una importació transaccional
Dins del bucle Cada element falla per separat; els altres continuen Processos tolerants: importar 1000 registres i descartar els trencats

El flux complet, dibuixat:

flowchart TB
    A["Entra al bloc try"] --> B["Executa sentencia"]
    B --> C{"Llanca excepcio?"}
    C -->|"no, i queden sentencies"| B
    C -->|"no, i el try acaba"| G["Salta TOTS els catch"]
    C -->|"si"| D["Abandona la resta del try<br/>(bucles i crides inclosos)"]
    D --> E{"Hi ha un catch<br/>de tipus compatible?"}
    E -->|"si"| F["Executa aquest catch<br/>L'excepcio queda consumida"]
    E -->|"no"| H["L'excepcio SEGUEIX pujant<br/>per la pila de crides"]
    F --> I["Continua despres del bloc"]
    G --> I

I un matís important: el try protegeix també tot el que passi dins dels mètodes que es cridin des d'ell, per molts nivells que hi hagi. Si gestor.prestar(...) crida cataleg.cercar(...) que crida index.get(...) i allà es llança una excepció, el teu catch la rep. Això és precisament el que fa útil el mecanisme: pots gestionar en un sol lloc les fallades d'un subarbre sencer de crides.

  1. L'objecte excepció i els seus mètodes

El paràmetre del catch és una referència a l'objecte excepció, amb tot el seu estat disponible. Aquests són els seus mètodes i per a què serveix cadascun:

Mètode Què retorna Ús típic
getMessage() El missatge de detall, o null si no en té Compondre un missatge de log
getLocalizedMessage() Igual, tret que la classe el sobreescrigui amb traducció Missatges internacionalitzats
toString() classe.completa: missatge Log compacte d'una línia
getClass().getSimpleName() El nom curt de la classe Distingir el tipus en un log
printStackTrace() (void) Imprimeix el trace a System.err Només depuració local, mai producció
getStackTrace() StackTraceElement[] amb els marcs Anàlisi programàtica de l'origen
getCause() L'excepció que la va provocar, o null Baixar a la causa arrel
getSuppressed() Throwable[] d'excepcions suprimides Només amb try-with-resources (06-06)
initCause(Throwable) Estableix la causa a posteriori Casos rars; millor el constructor (06-03)

Un catch que els fa servir tots, perquè vegis la diferència entre ells:

package com.nexussoftware.bibliotech.presentacio;

public class AnatomiaDelCatch {

    public static void main(String[] args) {
        try {
            calcularMultaEmpleat("EMP-004", "quinze");
        } catch (NumberFormatException e) {

            System.out.println("getMessage()        : " + e.getMessage());
            System.out.println("toString()          : " + e);
            System.out.println("getClass().getName(): " + e.getClass().getName());
            System.out.println("getClass().getSimpleName(): " + e.getClass().getSimpleName());
            System.out.println("getCause()          : " + e.getCause());
            System.out.println("getSuppressed().length: " + e.getSuppressed().length);

            System.out.println("\n-- Marcs de la pila (getStackTrace) --");
            StackTraceElement[] marcs = e.getStackTrace();
            for (int i = 0; i < Math.min(4, marcs.length); i++) {
                StackTraceElement m = marcs[i];
                System.out.printf("  [%d] %s.%s (%s:%d)%n",
                        i, m.getClassName(), m.getMethodName(),
                        m.getFileName(), m.getLineNumber());
            }

            // Localitzar el nostre propi codi dins del trace
            for (StackTraceElement m : marcs) {
                if (m.getClassName().startsWith("com.nexussoftware")) {
                    System.out.println("\nPrimer marc propi: "
                            + m.getMethodName() + " a la linia " + m.getLineNumber());
                    break;
                }
            }
        }
    }

    static void calcularMultaEmpleat(String idEmpleat, String diesText) {
        int dies = Integer.parseInt(diesText);
        System.out.println("Multa: " + (dies * 0.25));
    }
}

Sortida:

getMessage()        : For input string: "quinze"
toString()          : java.lang.NumberFormatException: For input string: "quinze"
getClass().getName(): java.lang.NumberFormatException
getClass().getSimpleName(): NumberFormatException
getCause()          : null
getSuppressed().length: 0

-- Marcs de la pila (getStackTrace) --
  [0] java.lang.NumberFormatException.forInputString (NumberFormatException.java:67)
  [1] java.lang.Integer.parseInt (Integer.java:665)
  [2] java.lang.Integer.parseInt (Integer.java:781)
  [3] com.nexussoftware.bibliotech.presentacio.AnatomiaDelCatch.calcularMultaEmpleat (AnatomiaDelCatch.java:38)

Primer marc propi: calcularMultaEmpleat a la linia 38

Dues advertències sobre aquests mètodes:

getMessage() pot retornar null. Ho vas veure a 06-01: UnsupportedOperationException i ConcurrentModificationException solen llançar-se sense missatge. Si concatenes directament obtindràs "Error: null", que és pitjor que res. Protegeix-te:

String detall = (e.getMessage() != null) ? e.getMessage() : e.getClass().getSimpleName();

O simplement fes servir e.toString(), que sempre inclou almenys el nom de la classe.

printStackTrace() no és logging. Escriu a System.err sense marca de temps, sense nivell, sense nom del fil, sense possibilitat de filtrar ni desactivar, i sense cap relació amb el sistema de registre de l'aplicació. Serveix per depurar a la teva màquina i per res més. A 06-07 el substituiràs per logger.log(Level.SEVERE, missatge, e), que fa el mateix però de manera utilitzable.

  1. Múltiples blocs catch i la regla d'ordre

Un mateix try pot portar diversos catch, cadascun per a un tipus diferent. La JVM els avalua en ordre de dalt a baix i executa el primer compatible, no el més específic.

package com.nexussoftware.bibliotech.presentacio;

import java.util.ArrayList;
import java.util.List;

public class DiversosCatch {

    public static void main(String[] args) {
        processar(args.length > 0 ? args[0] : "1");
    }

    static void processar(String cas) {
        List<String> cataleg = new ArrayList<>(List.of("Java Eficac", "Patrons de Disseny"));

        try {
            switch (cas) {
                case "1" -> System.out.println(Integer.parseInt("dotze"));  // NumberFormat
                case "2" -> System.out.println(cataleg.get(9));             // IndexOutOfBounds
                case "3" -> System.out.println(30 / 0);                     // Arithmetic
                default  -> System.out.println(((String) null).length());   // NullPointer
            }
        } catch (NumberFormatException e) {
            System.out.println("Entrada no numerica: " + e.getMessage());
        } catch (IndexOutOfBoundsException e) {
            System.out.println("Index fora de rang: " + e.getMessage());
        } catch (ArithmeticException e) {
            System.out.println("Error aritmetic: " + e.getMessage());
        } catch (RuntimeException e) {
            // Xarxa de seguretat per a la resta de no comprovades
            System.out.println("Una altra fallada en execucio: " + e);
        }
    }
}

I ara la regla que no és opcional:

Els catch s'han d'ordenar de la subclasse a la superclasse. Si un catch d'un tipus més general precedeix un altre de més específic, el codi NO COMPILA.

Comprova-ho:

try {
    Integer.parseInt("dotze");
} catch (RuntimeException e) {              // MES GENERAL primer
    System.out.println("General");
} catch (NumberFormatException e) {         // error de compilacio
    System.out.println("Especific");
}

El compilador rebutja el codi:

error: exception NumberFormatException has already been caught
    } catch (NumberFormatException e) {
      ^

El raonament és transparent: NumberFormatException és una RuntimeException, així que el primer catch ja l'hauria atrapada. El segon bloc seria codi inabastable, i Java rebutja el codi inabastable en temps de compilació en comptes de deixar-te creure que funciona.

És una de les poques vegades en què el compilador et protegeix d'un error de lògica, i convé agrair-ho: en llenguatges sense aquesta comprovació, un catch ample col·locat per descuit al principi s'empassa silenciosament tots els gestors específics que vénen després.

La jerarquia rellevant per ordenar bé:

flowchart TB
    RE["RuntimeException<br/>(el mes general dels habituals)"]
    RE --> IAE["IllegalArgumentException"]
    RE --> ISE["IllegalStateException"]
    RE --> IOB["IndexOutOfBoundsException"]
    RE --> NPE["NullPointerException"]
    IAE --> NFE["NumberFormatException<br/>(el mes especific)"]
    IOB --> AIOB["ArrayIndexOutOfBoundsException"]

    N1["Ordre CORRECTE dels catch:<br/>NumberFormatException,<br/>despres IllegalArgumentException,<br/>despres RuntimeException"]

Compte amb el parany d'aquest diagrama: NumberFormatException descendeix d'IllegalArgumentException, no directament de RuntimeException. Per això aquest ordre tampoc no compila:

catch (IllegalArgumentException e) { ... }
catch (NumberFormatException e)    { ... }   // ja capturada per l'anterior

És un parentiu que sorprèn molt. Si captures IllegalArgumentException per a les teves validacions de domini (06-03), tingues present que també estàs capturant totes les fallades de conversió numèrica, cosa que pot confondre dos problemes molt diferents.

  1. Multi-catch amb |

Quan dues o més excepcions es gestionen exactament igual, Java 7 permet combinar-les en un sol catch amb la barra vertical |:

try {
    int dies = Integer.parseInt(entrada);
    Material m = materials.get(dies);
    processar(m);
} catch (NumberFormatException | IndexOutOfBoundsException e) {
    System.out.println("Entrada invalida (" + e.getClass().getSimpleName() + "): " + e.getMessage());
}

Sense multi-catch hauries de duplicar el cos o extreure'l a un mètode privat. Amb ell, la intenció queda clara: aquestes dues fallades signifiquen el mateix per a mi.

El multi-catch té dues regles pròpies que cal conèixer:

Regla 1: els tipus no poden estar relacionats per herència.

// NO COMPILA
catch (NumberFormatException | IllegalArgumentException e) { ... }
// error: Alternatives in a multi-catch statement cannot be related by subclassing
//        Alternative NumberFormatException is a subclass of IllegalArgumentException

Té sentit: si captures IllegalArgumentException, la NumberFormatException ja hi està inclosa. Anomenar-la un altre cop seria redundant i probablement indicaria un malentès sobre la jerarquia.

Regla 2: la variable és implícitament final.

try {
    // ...
} catch (NumberFormatException | ArithmeticException e) {
    e = new RuntimeException("una altra cosa");   // NO COMPILA
    // error: multi-catch parameter e may not be assigned
}

En un catch normal sí que pots reassignar el paràmetre (encara que gairebé mai no hauries de fer-ho); en un multi-catch no. La raó és tècnica: el tipus estàtic d'e és el supertipus comú més proper de les alternatives, i permetre la reassignació complicaria l'anàlisi de tipus del compilador. A la pràctica, la restricció no molesta: reassignar el paràmetre d'un catch és una mala idea de totes maneres.

Quin tipus té e en un multi-catch. És l'avantpassat comú més proper de totes les alternatives. Amb NumberFormatException | IndexOutOfBoundsException, l'avantpassat comú és RuntimeException, així que dins del bloc només pots cridar els mètodes de RuntimeException (que són els de Throwable). Si necessites un mètode específic d'una d'elles, necessites catch separats o un instanceof:

} catch (NumberFormatException | java.io.IOException e) {
    // Aqui el tipus comu es Exception: nomes metodes de Throwable
    System.out.println(e.getMessage());

    // Si necessites alguna cosa especifica:
    if (e instanceof java.io.IOException io) {
        System.out.println("Fallada d'E/S, es reintentara mes tard");
    }
}

Quan fer servir cada forma:

Situació Forma
Mateix tractament per a diversos tipus no emparentats Multi-catch A | B
Tractament diferent per tipus catch separats, de l'específic al general
Un tipus i tots els seus descendents Un sol catch de la superclasse

  1. Blocs try imbricats

Un try pot contenir un altre try, i la cerca de gestor procedeix de dins cap a fora: primer els catch del try intern, i només si cap no és compatible es proven els de l'extern.

package com.nexussoftware.bibliotech.presentacio;

public class TryImbricats {

    public static void main(String[] args) {
        String[] linies = { "LIB-0001;15", "LIB-0002;dotze", "LIB-0003" };

        try {
            System.out.println("== Importacio del cataleg ==");

            for (String linia : linies) {
                String[] parts = linia.split(";");
                String referencia = parts[0];

                // TRY INTERN: un dia invalid no ha d'avortar la importacio
                int dies;
                try {
                    dies = Integer.parseInt(parts[1]);
                } catch (NumberFormatException e) {
                    System.out.println("  " + referencia + ": dies invalids, s'usa 15 per defecte");
                    dies = 15;
                }

                System.out.println("  " + referencia + " -> " + dies + " dies");
            }

            System.out.println("Importacio acabada");
        } catch (ArrayIndexOutOfBoundsException e) {
            // TRY EXTERN: una linia sense ';' SI que avorta la importacio (format corrupte)
            System.out.println("FITXER CORRUPTE: falta un camp. " + e.getMessage());
        }
    }
}

Sortida:

== Importacio del cataleg ==
  LIB-0001 -> 15 dies
  LIB-0002: dies invalids, s'usa 15 per defecte
  LIB-0002 -> 15 dies
FITXER CORRUPTE: falta un camp. Index 1 out of bounds for length 1

Aquest exemple mostra exactament quan té sentit imbricar: quan dues fallades diferents mereixen dos àmbits de recuperació diferents. Un número mal escrit és recuperable al lloc (fem servir un valor per defecte i seguim); una línia sense el separador significa que el fitxer sencer és sospitós i cal avortar.

Dit això, la imbricació és fàcil d'abusar. Dos o tres nivells fan el codi il·legible. Alternatives gairebé sempre preferibles:

  • Extreure el try intern a un mètode propi amb nom descriptiu (llegirDiesODefecte(String)), cosa que a més documenta la política de recuperació.
  • Fer servir try-with-resources (06-06) si la imbricació venia de tancar recursos.

  1. Abast de les variables declarades dins del try

Un detall de sintaxi que causa errors de compilació constants a qui comença:

try {
    int dies = Integer.parseInt(entrada);       // declarada DINS del try
} catch (NumberFormatException e) {
    System.out.println("Fallada");
}
System.out.println(dies);   // NO COMPILA: cannot find symbol

Les claus del try són un bloc com qualsevol altre: el que es declara a dins només existeix a dins. Si necessites la variable després, declara-la fora:

int dies;                                        // declarada fora
try {
    dies = Integer.parseInt(entrada);            // assignada dins
} catch (NumberFormatException e) {
    dies = DIES_PRESTEC;                         // valor per defecte al catch
}
System.out.println("Dies: " + dies);             // compila i funciona

I aquí hi ha una subtilesa que sí que convé entendre bé. Aquest codi no compila:

int dies;
try {
    dies = Integer.parseInt(entrada);
} catch (NumberFormatException e) {
    System.out.println("Fallada");               // el catch NO assigna dies
}
System.out.println(dies);   // error: variable dies might not have been initialized

El compilador aplica anàlisi d'assignació definitiva: per fer servir dies ha d'estar segur que s'ha assignat en tots els camins possibles. Si el try falla i el catch no assigna res, dies es queda sense valor. El compilador ho detecta i s'hi nega.

Tens tres sortides legals:

// A) Inicialitzar a la declaracio
int dies = DIES_PRESTEC;
try { dies = Integer.parseInt(entrada); } catch (NumberFormatException e) { /* es queda el default */ }

// B) Assignar tambe al catch
int dies;
try { dies = Integer.parseInt(entrada); } catch (NumberFormatException e) { dies = DIES_PRESTEC; }

// C) Sortir del metode al catch (return o throw): llavors no hi ha cami sense assignar
int dies;
try { dies = Integer.parseInt(entrada); } catch (NumberFormatException e) { return; }
System.out.println(dies);   // compila: si arribem aqui, dies te valor

L'opció B és la més explícita i la que millor documenta la intenció: es veu d'un cop d'ull quin és el valor de recuperació. L'A és més compacta però pot confondre qui llegeix, perquè el valor per defecte queda lluny del catch.

  1. Què es pot fer dins d'un catch

Aquesta és la pregunta que separa la gestió d'excepcions real del ritual buit. Un catch no hi és per "fer alguna cosa amb l'error": hi és per prendre una decisió. I les decisions sensates són quatre.

Estratègia Quan Risc
Recuperar-se amb un valor per defecte Existeix una alternativa raonable i el programa pot continuar correctament Amagar un problema real sota un valor plausible
Reintentar La fallada és transitòria (xarxa, bloqueig, fitxer ocupat) Bucle infinit si no es limita
Traduir a una altra excepció La fallada ha de pujar, però amb el vocabulari d'aquesta capa Perdre la causa original
Registrar i rellançar Aquí només es pot documentar; la decisió és de dalt Registrar dues vegades el mateix error

I una cinquena, que gairebé mai no és correcta però existeix: no fer res, quan la fallada és genuïnament irrellevant. Requereix un comentari obligatori que expliqui per què.

El que no és una estratègia: e.printStackTrace() a seques. Això no decideix res; només escup text i deixa el programa continuar com si res no hagués passat, amb l'estat possiblement trencat.

Vegem les quatre amb codi real.

  1. Estratègia 1: recuperar-se amb un valor per defecte

La més freqüent a la frontera amb l'usuari o amb la configuració. La fallada té una alternativa raonable i documentada:

package com.nexussoftware.bibliotech.presentacio;

/** Conversio tolerant per a l'entrada per consola de BiblioTech. */
public final class EntradaSegura {

    private EntradaSegura() { }   // classe d'utilitat: no s'instancia

    /**
     * Converteix text a enter. Si no es convertible, retorna el valor per defecte.
     *
     * Aquesta es la versio "tolerant", pensada per a la CAPA DE PRESENTACIO,
     * on una entrada dolenta de l'usuari es una cosa normal i no una fallada del sistema.
     * A la capa de domini la politica sera la contraria: llancar (06-03).
     */
    public static int aEnterODefecte(String text, int perDefecte) {
        if (text == null || text.isBlank()) {
            return perDefecte;
        }
        try {
            return Integer.parseInt(text.trim());
        } catch (NumberFormatException e) {
            // Recuperacio: el valor per defecte es una resposta VALIDA aqui.
            // S'informa l'usuari perque no cregui que la seva dada s'ha usat.
            System.out.println("  [avis] '" + text + "' no es un numero. S'usa " + perDefecte);
            return perDefecte;
        }
    }

    public static double aDecimalODefecte(String text, double perDefecte) {
        if (text == null || text.isBlank()) {
            return perDefecte;
        }
        try {
            // S'accepta la coma decimal, molt habitual en teclejar en catala
            return Double.parseDouble(text.trim().replace(',', '.'));
        } catch (NumberFormatException e) {
            System.out.println("  [avis] '" + text + "' no es un decimal. S'usa " + perDefecte);
            return perDefecte;
        }
    }

    public static void main(String[] args) {
        System.out.println(aEnterODefecte("15", 15));          // 15
        System.out.println(aEnterODefecte("dotze", 15));       // avis + 15
        System.out.println(aEnterODefecte("  20  ", 15));      // 20 (trim)
        System.out.println(aEnterODefecte(null, 15));          // 15
        System.out.println(aDecimalODefecte("0,25", 0.25));    // 0.25 (coma admesa)
    }
}

La condició perquè aquesta estratègia sigui legítima: el valor per defecte ha de ser una resposta correcta, no una tapadora. Retornar 15 quan l'usuari escriu "dotze" en un camp de dies de préstec és defensable, perquè 15 és el termini estàndard de BiblioTech i a més se l'avisa. Retornar 0 com a saldo de multa quan falla el càlcul no ho és: estaria inventant una dada de negoci.

  1. Estratègia 2: reintentar

Quan la fallada és transitòria —una lectura de xarxa que es talla, un fitxer temporalment bloquejat, un servei que respon tard—, reintentar és la resposta correcta. Amb tres condicions innegociables: límit d'intents, espera entre ells i fallada definitiva en esgotar-los.

package com.nexussoftware.bibliotech.servei;

/**
 * Reintent amb espera creixent (backoff) per a una operacio transitoria.
 *
 * Nota: l'operacio se simula. El cas real (llegir un fitxer, cridar un
 * servei) arriba als moduls 7 i 9; el que importa aqui es l'ESTRUCTURA.
 */
public class SincronitzadorCataleg {

    private static final int MAX_INTENTS = 4;
    private static final long ESPERA_BASE_MS = 200;

    private int crides = 0;

    /** Simula una operacio que falla les dues primeres vegades i despres funciona. */
    private String descarregarCatalegRemot() {
        crides++;
        if (crides < 3) {
            throw new IllegalStateException("Servidor no disponible (intent " + crides + ")");
        }
        return "3 materials sincronitzats";
    }

    public String sincronitzar() {
        IllegalStateException ultimaFallada = null;

        for (int intent = 1; intent <= MAX_INTENTS; intent++) {
            try {
                String resultat = descarregarCatalegRemot();
                System.out.println("OK a l'intent " + intent + ": " + resultat);
                return resultat;                         // exit: se surt del bucle
            } catch (IllegalStateException e) {
                ultimaFallada = e;                       // es guarda per a la fallada definitiva
                System.out.println("Intent " + intent + " fallit: " + e.getMessage());

                if (intent == MAX_INTENTS) {
                    break;                               // no esperar despres de l'ultim intent
                }

                long espera = ESPERA_BASE_MS * (1L << (intent - 1));   // 200, 400, 800 ms
                System.out.println("  Esperant " + espera + " ms abans de reintentar...");
                try {
                    Thread.sleep(espera);
                } catch (InterruptedException ie) {
                    // Bona practica del modul 8: restaurar el marcador d'interrupcio
                    // i abandonar. Mai no s'ha d'empassar una InterruptedException.
                    Thread.currentThread().interrupt();
                    throw new IllegalStateException("Sincronitzacio interrompuda", ie);
                }
            }
        }

        // Esgotats els intents: fallar de manera clara, conservant la causa (06-03)
        throw new IllegalStateException(
                "No s'ha pogut sincronitzar el cataleg despres de " + MAX_INTENTS + " intents",
                ultimaFallada);
    }

    public static void main(String[] args) {
        System.out.println(new SincronitzadorCataleg().sincronitzar());
    }
}

Sortida:

Intent 1 fallit: Servidor no disponible (intent 1)
  Esperant 200 ms abans de reintentar...
Intent 2 fallit: Servidor no disponible (intent 2)
  Esperant 400 ms abans de reintentar...
OK a l'intent 3: 3 materials sincronitzats
3 materials sincronitzats

Quatre detalls que fan que aquest patró sigui correcte i no una font de desastres:

  1. Límit explícit (MAX_INTENTS). Un while (true) amb reintents és una bomba: si el servei està caigut de debò, el programa gira per sempre consumint CPU.
  2. Espera creixent (exponential backoff): 200, 400, 800 ms. Reintentar immediatament en bucle agreuja la caiguda del servei remot, perquè li afegeixes càrrega justament quan està en apurs.
  3. En esgotar els intents, es falla. No es retorna null ni un valor buit fingint èxit. I es conserva l'última fallada com a causa, perquè el stack trace mostri el Caused by:.
  4. Només es reintenten fallades transitòries. Reintentar una NumberFormatException o una IllegalArgumentException és absurd: la dada continuarà sent invàlida al quart intent. La regla: reintenta el que depèn de l'entorn, mai el que depèn de les dades.

  1. Estratègia 3: traduir a una altra excepció

Quan la fallada ha de continuar pujant, però expressada amb el vocabulari de la teva capa. Un Cataleg no hauria d'obligar la capa de presentació a entendre d'índexs de mapes ni de formats de fitxer.

// A la capa de servei de BiblioTech
public Material carregarMaterialDesDeLinia(String linia) {
    try {
        String[] camps = linia.split(";");
        String referencia = camps[0];
        int any = Integer.parseInt(camps[2]);
        return new Llibre(referencia, camps[1], any);
    } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
        // TRADUCCIO: qui crida no te per que saber que a dins
        // hi havia un split ni un parseInt. El que li importa es que
        // aquesta linia del cataleg esta mal formada.
        throw new IllegalArgumentException(
                "Linia de cataleg mal formada: '" + linia + "'", e);    // <-- la causa, conservada
    }
}

La paraula clau és el segon argument del constructor: e. És la causa, i és el que produeix el Caused by: del stack trace que vas estudiar a 06-01. Sense ell, la informació de què ha fallat exactament desapareix.

La tècnica completa —encadenament d'excepcions, traducció entre capes, quan embolcallar i quan rellançar— és el cor de la lliçó 06-03, i les excepcions de domini a les quals traduiràs (MaterialNoTrobatException i companyia) arriben a 06-04. Aquí n'hi ha prou que retinguis la forma i el motiu.

  1. Estratègia 4: registrar i rellançar

Quan en aquest punt només pots deixar constància, però la decisió de què fer correspon a una capa superior:

public void processarDevolucio(String referencia, int dia) {
    try {
        registre.retornar(referencia, dia);
    } catch (RuntimeException e) {
        // Es deixa constancia AMB el context que nomes es coneix aqui...
        System.err.println("Fallada en retornar " + referencia + " el dia " + dia);
        // ...i es deixa pujar perque a dalt decideixin.
        throw e;                              // rellancar la MATEIXA excepcio
    }
}

throw e; rellança l'objecte original intacte: mateixa classe, mateix missatge, mateixa pila. No s'afegeix un marc nou al stack trace, així que la línia de la fallada original es conserva.

El perill d'aquest patró té nom: el log duplicat. Si cada capa registra i rellança, una sola fallada apareix cinc vegades al registre amb cinc stack traces gairebé idèntics, i trobar la primera ocurrència es torna una tasca arqueològica. La regla que aplicaràs a 06-07:

Registra on gestiones. Si vas a rellançar, no registris el stack trace complet: com a molt, afegeix context.

  1. Antipatrons, amb demostració

Els tres que fan més mal, amb la demostració de per què.

El catch buit

package com.nexussoftware.bibliotech.presentacio;

import java.util.ArrayList;
import java.util.List;

public class DemoCatchBuit {

    static List<String> registrats = new ArrayList<>();

    /** VERSIO DOLENTA: el catch buit destrueix la informacio de la fallada. */
    static void registrarMalament(String linia) {
        try {
            String[] parts = linia.split(";");
            int copies = Integer.parseInt(parts[1]);
            for (int i = 0; i < copies; i++) {
                registrats.add(parts[0] + "-" + i);
            }
        } catch (Exception e) {
            // buit a proposit
        }
    }

    public static void main(String[] args) {
        registrarMalament("LIB-0001;2");
        registrarMalament("LIB-0002;dos");   // falla... i NINGU no se n'assabenta
        registrarMalament("LIB-0003");       // falla... i NINGU no se n'assabenta
        registrarMalament("LIB-0004;1");

        System.out.println("Registrats: " + registrats);
        System.out.println("Total esperat: 6 exemplars. Total real: " + registrats.size());
    }
}

Sortida:

Registrats: [LIB-0001-0, LIB-0001-1, LIB-0004-0]
Total esperat: 6 exemplars. Total real: 3

Això és exactament el que passa en producció: el programa acaba sense error, amb un resultat incorrecte i sense la més mínima pista de què ha passat. No hi ha stack trace, no hi ha log, no hi ha codi de sortida diferent de zero. Una fallada així pot viure mesos en un sistema, corrompent dades a poc a poc, fins que algú nota que els números no quadren. I per llavors no queda cap rastre a seguir.

Un catch buit és pitjor que no capturar res. Sense catch, el programa cau sorollosament i saps exactament on. Amb ell, menteixes.

El catch (Exception e) massa ample

// MALAMENT: la xarxa s'endu peixos que no voliem pescar
try {
    Material m = cataleg.cercarPerReferencia(referencia);
    int dies = Integer.parseInt(entradaUsuari);
    gestor.prestar(m, empleat, dies);
} catch (Exception e) {
    System.out.println("Entrada invalida, torneu-ho a provar");
}

El missatge diu "entrada invàlida", però aquell catch atrapa també un NullPointerException per un bug teu, un IllegalStateException perquè el registre està corrupte i qualsevol fallada de programació de la resta del bloc. L'usuari rebrà "entrada invàlida" davant d'un defecte greu del sistema, i tornarà a teclejar la seva dada correcta una vegada i una altra preguntant-se què fa malament.

La versió correcta captura el que sap gestionar i deixa pujar la resta:

try {
    Material m = cataleg.cercarPerReferencia(referencia);
    int dies = Integer.parseInt(entradaUsuari);
    gestor.prestar(m, empleat, dies);
} catch (NumberFormatException e) {
    System.out.println("El nombre de dies no es valid: " + entradaUsuari);
}
// Un NullPointerException puja i es veu. Es un bug, i s'ha de notar.

La regla: captura el tipus més específic que sàpigues gestionar. Un catch (Exception e) només és legítim a la frontera d'errors de l'aplicació —el gestor global del main—, que és on de debò cal impedir que alguna cosa escapi sense registrar-se. Això ho muntaràs a 06-07.

Fer servir excepcions per al flux normal

// PROHIBIT: recorrer provocant l'excepcio de final
try {
    int i = 0;
    while (true) {
        System.out.println(materials.get(i++).getTitol());
    }
} catch (IndexOutOfBoundsException fi) {
    // "ja hem acabat"
}

A més de ser fosc, és lent. I això té números.

  1. El cost real d'una excepció enfront d'un if

La part cara d'una excepció no és llançar-la ni capturar-la: és construir-la, perquè el constructor de Throwable crida fillInStackTrace(), que recorre i copia la pila de crides completa. Com més profunda sigui la pila, més costa.

package com.nexussoftware.bibliotech.presentacio;

/**
 * Comparativa illustrativa: comprovar amb if enfront de provocar i capturar.
 * ATENCIO: es un mesurament casola. Per mesurar de debo cal JMH (11-07),
 * perque el JIT distorsiona els micro-benchmarks manuals.
 */
public class CostDeLesExcepcions {

    private static final int VOLTES = 1_000_000;

    public static void main(String[] args) {
        String[] entrades = new String[VOLTES];
        for (int i = 0; i < VOLTES; i++) {
            entrades[i] = (i % 2 == 0) ? String.valueOf(i) : "no-numero";   // 50% invalides
        }

        // A) Comprovar abans amb una validacio manual
        long t0 = System.nanoTime();
        int validsA = 0;
        for (String s : entrades) {
            if (esEnterSimple(s)) { validsA++; }
        }
        long tA = (System.nanoTime() - t0) / 1_000_000;

        // B) Intentar i capturar l'excepcio
        long t1 = System.nanoTime();
        int validsB = 0;
        for (String s : entrades) {
            try {
                Integer.parseInt(s);
                validsB++;
            } catch (NumberFormatException e) {
                // ignorat a proposit per al mesurament
            }
        }
        long tB = (System.nanoTime() - t1) / 1_000_000;

        // C) Excepcio SENSE stack trace (veure 06-04): quant costa nomes el mecanisme
        long t2 = System.nanoTime();
        int validsC = 0;
        for (String s : entrades) {
            try {
                if (!esEnterSimple(s)) { throw SENSE_TRACA; }
                validsC++;
            } catch (RuntimeException e) {
                // ignorat
            }
        }
        long tC = (System.nanoTime() - t2) / 1_000_000;

        System.out.println("A) if previ                      : " + tA + " ms (" + validsA + ")");
        System.out.println("B) try/catch amb parseInt        : " + tB + " ms (" + validsB + ")");
        System.out.println("C) excepcio sense stack trace    : " + tC + " ms (" + validsC + ")");
    }

    /** Excepcio preconstruida i SENSE stack trace: gairebe gratis de llancar. */
    private static final RuntimeException SENSE_TRACA =
            new RuntimeException("no numeric", null, false, false);

    private static boolean esEnterSimple(String s) {
        if (s == null || s.isEmpty()) { return false; }
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (i == 0 && (c == '-' || c == '+') && s.length() > 1) { continue; }
            if (c < '0' || c > '9') { return false; }
        }
        return true;
    }
}

Resultats típics (una màquina qualsevol; els teus variaran, però l'ordre de magnitud es manté):

A) if previ                      : 12 ms (500000)
B) try/catch amb parseInt        : 890 ms (500000)
C) excepcio sense stack trace    : 18 ms (500000)
Enfocament Temps relatiu Interpretació
A) if previ 1x El cost base
B) Excepció completa ~70x Dominat per fillInStackTrace
C) Excepció sense traça ~1,5x El mecanisme de throw/catch en si és barat

Les conclusions correctes —i una d'incorrecta que cal descartar:

  • El throw/catch en si no és car. El car és capturar la pila en construir l'objecte. La comparació A contra C ho demostra.
  • Per tant, fer servir excepcions per al que és excepcional no té cost apreciable. Si un error passa un cop cada mil operacions, aquests 900 nanosegons són irrellevants.
  • Fer-les servir per al flux normal sí que en té. Un milió d'excepcions en un bucle és un problema mesurable.
  • Conclusió incorrecta que NO has de treure: "les excepcions són lentes, millor retornar false". L'eix de la decisió és la claredat i la correcció, no aquests nanosegons. Només en un bucle molt calent amb fallades molt freqüents la diferència comença a importar, i per a aquest cas rar existeix el truc del constructor writableStackTrace = false que veuràs a 06-04.

Un avís metodològic: aquest benchmark casolà és orientatiu. El JIT pot eliminar codi mort, alinear mètodes i esbiaixar el resultat. Per mesurar de debò s'usa JMH (11-07). El que sí que és sòlid aquí és la comparació relativa entre B i C, perquè aïlla el factor que domina.

  1. BiblioTech: el menú que ja no es trenca

És hora de cobrar la promesa. Aquest era el problema del menú de 02-06:

// Versio del modul 2: l'usuari escriu "dotze" i l'aplicacio MOR
System.out.print("Opcio: ");
int opcio = Integer.parseInt(scanner.nextLine());    // NumberFormatException -> adeu

I aquesta és la versió amb gestió d'errors. Fixa't que la política és diferent segons el tipus de dada: l'opció del menú es reintenta (l'usuari hi és, pot corregir), i les dades numèriques amb valor raonable es recuperen amb un defecte.

package com.nexussoftware.bibliotech.presentacio;

import java.util.Scanner;

/**
 * Menu de BiblioTech resistent a l'entrada de l'usuari.
 *
 * Politica d'errors d'aquesta classe (capa de presentacio):
 *  - Opcio del menu invalida  -> reintentar fins que sigui valida.
 *  - Dada numerica invalida   -> reintentar amb limit; si s'esgota, cancellar l'operacio.
 *  - Qualsevol altra fallada  -> es deixa pujar (encara). A 06-07 es gestionara a la frontera.
 */
public class MenuBiblioTech {

    private static final int MAX_REINTENTS = 3;
    private static final int DIES_PRESTEC = 15;

    private final Scanner scanner = new Scanner(System.in);

    public void arrencar() {
        boolean sortir = false;
        while (!sortir) {
            mostrarMenu();
            int opcio = llegirOpcio(0, 4);

            switch (opcio) {
                case 1 -> llistarCataleg();
                case 2 -> prestarMaterial();
                case 3 -> retornarMaterial();
                case 4 -> calcularMulta();
                case 0 -> { sortir = true; System.out.println("Fins aviat."); }
                default -> System.out.println("Opcio no contemplada.");
            }
        }
    }

    private void mostrarMenu() {
        System.out.println("""

                ===== BiblioTech - Nexus Software =====
                1. Llistar cataleg
                2. Prestar material
                3. Retornar material
                4. Calcular multa
                0. Sortir
                =======================================""");
    }

    /**
     * Llegeix una opcio valida, reintentant indefinidament.
     *
     * El reintent sense limit es acceptable AQUI perque hi ha una persona a l'altra
     * banda que pot corregir. En un proces per lots seria un bucle infinit:
     * la mateixa linia invalida es rellegiria per sempre.
     */
    private int llegirOpcio(int min, int max) {
        while (true) {
            System.out.print("Opcio: ");
            String linia = scanner.nextLine();

            try {
                int valor = Integer.parseInt(linia.trim());
                if (valor < min || valor > max) {
                    System.out.println("  Ha d'estar entre " + min + " i " + max + ". Reintenteu.");
                    continue;                       // dada valida com a numero, invalida com a opcio
                }
                return valor;
            } catch (NumberFormatException e) {
                // RECUPERACIO PER REINTENT: s'informa amb la dada culpable
                System.out.println("  '" + linia + "' no es un numero. Escriviu un digit de "
                        + min + " a " + max + ".");
            }
        }
    }

    /**
     * Llegeix un enter amb limit de reintents.
     * Retorna -1 si l'usuari esgota els intents, perque qui el cridi cancelli.
     *
     * (Aquest -1 encara es un "codi de retorn" dels que va criticar 06-01. Es
     *  acceptable dins d'una mateixa classe de presentacio; a la capa de
     *  servei se substituira per excepcions de domini a 06-04.)
     */
    private int llegirEnterAmbLimit(String peticio, int min, int max) {
        for (int intent = 1; intent <= MAX_REINTENTS; intent++) {
            System.out.print(peticio + ": ");
            String linia = scanner.nextLine();

            try {
                int valor = Integer.parseInt(linia.trim());
                if (valor < min || valor > max) {
                    System.out.printf("  Fora de rang [%d..%d]. Intent %d de %d.%n",
                            min, max, intent, MAX_REINTENTS);
                    continue;
                }
                return valor;
            } catch (NumberFormatException e) {
                System.out.printf("  '%s' no es un numero. Intent %d de %d.%n",
                        linia, intent, MAX_REINTENTS);
            }
        }
        System.out.println("  Massa intents fallits. Operacio cancellada.");
        return -1;
    }

    private double llegirDecimalODefecte(String peticio, double perDefecte) {
        System.out.print(peticio + " [" + perDefecte + "]: ");
        String linia = scanner.nextLine().trim();

        if (linia.isEmpty()) {
            return perDefecte;                       // Enter directe: accepta el valor proposat
        }
        try {
            return Double.parseDouble(linia.replace(',', '.'));
        } catch (NumberFormatException e) {
            System.out.println("  Valor no numeric. S'usa " + perDefecte);
            return perDefecte;
        }
    }

    private void llistarCataleg() {
        System.out.println("  (cataleg: 3 materials)");
    }

    private void prestarMaterial() {
        System.out.print("Referencia del material: ");
        String referencia = scanner.nextLine().trim();

        int dies = llegirEnterAmbLimit("Dies de prestec (1-30)", 1, 30);
        if (dies == -1) {
            return;                                  // cancellat: no es toca l'estat
        }
        System.out.println("  Prestat " + referencia + " durant " + dies + " dies.");
    }

    private void retornarMaterial() {
        System.out.print("Referencia a retornar: ");
        String referencia = scanner.nextLine().trim();

        int dia = llegirEnterAmbLimit("Dia de devolucio (1-365)", 1, 365);
        if (dia == -1) {
            return;
        }
        System.out.println("  Retornat " + referencia + " el dia " + dia + ".");
    }

    private void calcularMulta() {
        int diesRetard = llegirEnterAmbLimit("Dies de retard (0-200)", 0, 200);
        if (diesRetard == -1) {
            return;
        }
        double tarifa = llegirDecimalODefecte("Tarifa diaria", 0.25);

        double multa = Math.min(diesRetard * tarifa, 20.0);    // MULTA_MAXIMA
        System.out.printf("  Multa: %.2f EUR (termini estandard %d dies)%n", multa, DIES_PRESTEC);
    }

    public static void main(String[] args) {
        new MenuBiblioTech().arrencar();
    }
}

Sessió d'exemple:

===== BiblioTech - Nexus Software =====
1. Llistar cataleg
...
Opcio: dos
  'dos' no es un numero. Escriviu un digit de 0 a 4.
Opcio: 9
  Ha d'estar entre 0 i 4. Reintenteu.
Opcio: 4
Dies de retard (0-200): molts
  'molts' no es un numero. Intent 1 de 3.
Dies de retard (0-200): 22
Tarifa diaria [0.25]:
  Multa: 5.50 EUR (termini estandard 15 dies)

Tres decisions de disseny que val la pena assenyalar, perquè són les que separen aquest codi del "posar un try per si de cas":

  1. La política d'error depèn del context, no del tipus d'excepció. La mateixa NumberFormatException es tracta de tres maneres diferents: reintent infinit per a l'opció del menú (hi ha un humà), reintent limitat per a les dades (evita quedar-se encallat), i valor per defecte per a la tarifa (hi ha un valor raonable).
  2. La cancel·lació deixa l'estat intacte. Quan llegirEnterAmbLimit esgota els intents, prestarMaterial fa return abans de tocar res. No hi ha préstec a mitges. És un avançament de la coherència d'estat que resoldràs del tot a 06-05.
  3. Es captura només NumberFormatException, no Exception. Si dins del menú aparegués un NullPointerException per un bug, pujaria i es veuria. Amagar-lo sota "entrada invàlida" seria mentir a l'usuari i a tu mateix.

Queda una fragilitat evident que aquesta lliçó no pot resoldre: la capa de servei (GestorPrestecs, Cataleg) continua retornant null i false. Per arreglar això cal llançar excepcions, no només capturar-les. És la lliçó següent.

Errors Comuns i Consells

Capturar un tipus que el try no pot llançar. Amb excepcions comprovades, el compilador ho rebutja (exception X is never thrown in body of corresponding try statement). Amb les no comprovades, compila silenciosament i el catch no s'executa mai: és codi mort que dona falsa sensació de seguretat.

Posar catch (Exception e) per costum. Atrapa fallades que no saps gestionar i converteix bugs en missatges tranquil·litzadors. Captura el tipus més específic i deixa pujar la resta.

Invertir l'ordre dels catch. El general abans que l'específic no compila: exception X has already been caught. Recorda que NumberFormatException descendeix d'IllegalArgumentException, un parentiu que sorprèn.

Posar el try al voltant del bucle quan volies que cada element fallés per separat. La primera fallada avorta la resta del procés. Si vols tolerància element a element, el try va dins.

Fer servir una variable declarada dins del try després del bloc. No existeix fora. I si la declares fora però només l'assignes dins del try, el compilador exigirà que el catch també l'assigni, o que surti del mètode.

e.printStackTrace() com a gestió. No és gestió, és un abocament de text a System.err. En producció es perd, no es pot filtrar i no porta ni marca de temps ni context. A 06-07 el substitueixes per un Logger.

Concatenar e.getMessage() sense comprovar null. Diverses excepcions estàndard no porten missatge, i obtindràs "Error: null". Fes servir e.toString() o un valor alternatiu.

Reintentar una fallada que no és transitòria. Reintentar una NumberFormatException amb la mateixa dada és girar en el buit. Reintenta el que depèn de l'entorn; mai el que depèn de les dades.

Reintentar sense límit en un procés automàtic. Amb un humà davant és acceptable; en un procés per lots és un bucle infinit garantit.

Empassar-se una InterruptedException. Si captures una interrupció i no fas res, trenques el mecanisme de cancel·lació de fils del mòdul 8. El mínim és Thread.currentThread().interrupt() abans de sortir.

Consell: un catch que no pren cap decisió és un catch mal posat. Recuperar-se, reintentar, traduir o registrar i rellançar. Si no fas cap de les quatre, probablement no havies de capturar allà.

Consell: quan informis l'usuari, inclou la dada culpable. "'dotze' no es un numero" orienta; "Entrada invalida" no. El cost és el mateix.

Consell: comenta tot catch que quedi buit a propòsit. Si de debò la fallada és irrellevant, escriu per què. Un catch buit sense comentari és indistingible d'un oblit, i el següent que llegeixi el codi no sabrà si arreglar-lo.

Exercicis

Exercici 1: importador tolerant del catàleg

Escriu ImportadorCataleg a com.nexussoftware.bibliotech.servei, que rebi un String[] de línies amb el format referencia;titol;any;exemplars i produeixi un informe d'importació.

Requisits:

  • Processa cada línia de manera independent: un error en una no ha d'impedir processar les altres.
  • Tracta de manera diferent almenys tres tipus d'error:
    • Falta algun camp → ArrayIndexOutOfBoundsException → es descarta la línia amb motiu.
    • L'any o els exemplars no són numèrics → NumberFormatException → es descarta amb motiu.
    • L'any està fora de [1450, 2100] o els exemplars són <= 0no hi ha excepció: valida amb un if i descarta amb motiu.
  • Fes servir multi-catch on el tractament sigui idèntic, i catch separats on no ho sigui.
  • Retorna un record InformeImportacio(int acceptades, int rebutjades, List<String> motius).
  • Afegeix un comptador de línies processades que sigui correcte fins i tot quan fallen.

Prova amb almenys: una línia correcta, una sense camps suficients, una amb any "mil nou-cents", una amb any 1200, una amb 0 exemplars i una línia buida.

Exercici 2: caça de l'antipatró

Aquest mètode existeix de debò al BiblioTech heretat. Té sis defectes relacionats amb la gestió d'excepcions. Troba'ls, explica el mal que fa cadascun i reescriu el mètode correctament.

public double calcularMultaTotal(List<String> referencies, Map<String, Integer> diesRetard) {
    double total = 0;
    try {
        for (String ref : referencies) {
            try {
                int dies = diesRetard.get(ref);
                if (dies > 0) {
                    total += dies * 0.25;
                }
            } catch (Exception e) {
            }
        }
        int mitjana = (int) total / referencies.size();
        System.out.println("Mitjana: " + mitjana);
    } catch (Throwable t) {
        t.printStackTrace();
        return 0;
    }
    return total;
}

Exercici 3: lector de configuració amb política per clau

Escriu ConfiguracioBiblioTech, que llegeixi paràmetres d'un Map<String, String> (simulant un fitxer de propietats, que arriba a 07-07) i apliqui una política d'error diferent segons la criticitat de cada paràmetre:

  • llegirEnterObligatori(clau): si falta o no és numèric, llança IllegalStateException amb un missatge que identifiqui la clau i el valor trobat, conservant la causa quan n'hi hagi.
  • llegirEnterOpcional(clau, perDefecte): si falta o no és numèric, retorna el defecte i informa per consola.
  • llegirDecimalAmbRang(clau, min, max, perDefecte): a més de convertir, valida el rang; fora de rang o no numèric, retorna el defecte avisant del motiu concret (distingeix "no numèric" de "fora de rang").
  • llegirBoolea(clau, perDefecte): accepta "true"/"false"/"si"/"no"/"1"/"0" sense distingir majúscules; qualsevol altra cosa, defecte amb avís.

Al main, carrega una configuració amb valors vàlids, invàlids i absents —incloent-hi dies.prestec=15, tarifa.diaria=zero, multa.maxima=999, llindar.lleu absent i avisos.actius=SI— i demostra les quatre polítiques. Mostra també què passa quan falta un paràmetre obligatori, capturant-lo al main i mostrant un missatge digne.

Solucions

Solució 1

package com.nexussoftware.bibliotech.servei;

import java.util.ArrayList;
import java.util.List;

/**
 * Importa linies de cataleg tolerant errors linia a linia.
 *
 * Clau del disseny: el try va DINS del bucle, perque una linia
 * defectuosa no avorti la importacio completa.
 */
public class ImportadorCataleg {

    /** Resultat de la importacio (record: 04-07). */
    public record InformeImportacio(int processades, int acceptades,
                                    int rebutjades, List<String> motius) {

        public String resum() {
            StringBuilder sb = new StringBuilder();
            sb.append("=== INFORME D'IMPORTACIO ===\n");
            sb.append("Processades: ").append(processades).append('\n');
            sb.append("Acceptades : ").append(acceptades).append('\n');
            sb.append("Rebutjades : ").append(rebutjades).append('\n');
            if (!motius.isEmpty()) {
                sb.append("--- Motius de rebuig ---\n");
                for (String m : motius) {
                    sb.append("  * ").append(m).append('\n');
                }
            }
            return sb.toString();
        }
    }

    private static final int ANY_MIN = 1450;
    private static final int ANY_MAX = 2100;

    private final List<String> acceptats = new ArrayList<>();

    public InformeImportacio importar(String[] linies) {
        List<String> motius = new ArrayList<>();
        int processades = 0;
        int acceptades = 0;

        for (String linia : linies) {
            processades++;                      // s'incrementa SEMPRE, falli o no

            // Validacio previa: una linia buida no mereix ni intentar-se.
            // Regla general: si es pot comprovar amb un if barat, no usis excepcio.
            if (linia == null || linia.isBlank()) {
                motius.add("Linia " + processades + ": buida");
                continue;
            }

            try {
                String[] camps = linia.split(";");

                // Si falten camps, aquest acces llanca ArrayIndexOutOfBoundsException
                String referencia = camps[0].trim();
                String titol      = camps[1].trim();
                int any           = Integer.parseInt(camps[2].trim());
                int exemplars     = Integer.parseInt(camps[3].trim());

                // Validacions de negoci: NO llancen, es comproven amb if.
                // Un valor fora de rang no es una fallada tecnica, es una dada incorrecta.
                if (any < ANY_MIN || any > ANY_MAX) {
                    motius.add("Linia " + processades + " (" + referencia + "): any fora de rang ["
                            + ANY_MIN + ".." + ANY_MAX + "]: " + any);
                    continue;
                }
                if (exemplars <= 0) {
                    motius.add("Linia " + processades + " (" + referencia
                            + "): exemplars ha de ser positiu, era " + exemplars);
                    continue;
                }
                if (referencia.isEmpty() || titol.isEmpty()) {
                    motius.add("Linia " + processades + ": referencia o titol buits");
                    continue;
                }

                acceptats.add(referencia + " | " + titol + " | " + any + " | " + exemplars);
                acceptades++;

            } catch (ArrayIndexOutOfBoundsException e) {
                // CATCH ESPECIFIC 1: falten camps. Missatge diferent de l'altre cas.
                motius.add("Linia " + processades + ": falten camps (se n'esperaven 4). Contingut: '"
                        + linia + "'");

            } catch (NumberFormatException e) {
                // CATCH ESPECIFIC 2: camps numerics mal escrits.
                // e.getMessage() ja diu quin era el text ofensiu.
                motius.add("Linia " + processades + ": camp numeric invalid -> " + e.getMessage());
            }
        }

        return new InformeImportacio(processades, acceptades, processades - acceptades, motius);
    }

    public List<String> getAcceptats() {
        return List.copyOf(acceptats);          // copia immutable: encapsulament (03-07)
    }

    public static void main(String[] args) {
        String[] linies = {
                "LIB-0001;Java Eficac;2018;3",              // correcta
                "LIB-0002;Patrons de Disseny",              // falten camps
                "LIB-0003;Refactoritzacio;mil nou-cents;2", // any no numeric
                "LIB-0004;Llibre Antic;1200;1",             // any fora de rang
                "LIB-0005;Sense Exemplars;2020;0",          // exemplars invalids
                "",                                          // buida
                "LIB-0006;Java Concurrent;2021;dos",        // exemplars no numerics
                "LIB-0007;Clean Code;2008;5"                // correcta
        };

        ImportadorCataleg importador = new ImportadorCataleg();
        InformeImportacio informe = importador.importar(linies);

        System.out.println(informe.resum());
        System.out.println("--- Acceptats ---");
        importador.getAcceptats().forEach(a -> System.out.println("  " + a));
    }
}

Sortida:

=== INFORME D'IMPORTACIO ===
Processades: 8
Acceptades : 2
Rebutjades : 6
--- Motius de rebuig ---
  * Linia 2: falten camps (se n'esperaven 4). Contingut: 'LIB-0002;Patrons de Disseny'
  * Linia 3: camp numeric invalid -> For input string: "mil nou-cents"
  * Linia 4 (LIB-0004): any fora de rang [1450..2100]: 1200
  * Linia 5 (LIB-0005): exemplars ha de ser positiu, era 0
  * Linia 6: buida
  * Linia 7: camp numeric invalid -> For input string: "dos"

--- Acceptats ---
  LIB-0001 | Java Eficac | 2018 | 3
  LIB-0007 | Clean Code | 2008 | 5

Nota de disseny: fixa't que no s'ha fet servir multi-catch. En escriure els missatges es va veure que "falten camps" i "camp numèric invàlid" mereixen textos diferents, així que els catch separats són l'elecció correcta. El multi-catch hauria estat apropiat si el motiu fos simplement "linia mal formada" en tots dos casos. És la regla de l'apartat 5 en acció: multi-catch quan el tractament és idèntic, no quan els tipus són semblants.

Solució 2

Els sis defectes:

# Defecte Mal
1 catch (Exception e) { } buit al bucle intern Cada referència absent al mapa es descarta en silenci. El total surt malament i ningú no se n'assabenta
2 catch (Exception e) massa ample Atrapa el NullPointerException de l'autounboxing, però també qualsevol bug de programació
3 diesRetard.get(ref) desempaquetat sense comprovar Si la clau no existeix, get retorna null, s'intenta intValue() i salta NullPointerException. És exactament la fallada que el catch buit amaga
4 catch (Throwable t) Atrapa OutOfMemoryError i StackOverflowError, dels quals no es pot recuperar
5 t.printStackTrace() com a gestió No és logging; en producció es perd
6 return 0 després de la fallada Retorna una quantitat de multa inventada. Un zero indistingible de "no hi ha multa". A més, referencies.size() pot ser 0 i llançar ArithmeticException, que aquell catch(Throwable) dissimula

Hi ha un setè problema de fons, més greu que els sis: el mètode calcula una mitjana que no retorna ni usa, només la imprimeix. Aquesta barreja de càlcul i presentació és el que obliga a posar el println a dins i el que fa que la divisió per zero es coli en un mètode de càlcul.

Versió corregida:

package com.nexussoftware.bibliotech.servei;

import java.util.List;
import java.util.Map;
import java.util.Objects;

public class CalculadoraMultes {

    private static final double TARIFA_DIARIA = 0.25;
    private static final double MULTA_MAXIMA  = 20.0;

    /**
     * Suma la multa de les referencies indicades.
     *
     * Politica d'errors:
     *  - Arguments nuls: fallada de programacio, es llanca (fail-fast, 06-03).
     *  - Referencia sense dada de retard: es considera 0 dies, comprovant-ho
     *    amb un if. No es una fallada: es un cas normal (encara no ha vencut).
     *  - No es captura RES aqui: aquest metode no sap que fer amb una fallada.
     *    Qui el cridi decidira.
     */
    public double calcularMultaTotal(List<String> referencies, Map<String, Integer> diesRetard) {
        Objects.requireNonNull(referencies, "La llista de referencies no pot ser nulla");
        Objects.requireNonNull(diesRetard, "El mapa de dies de retard no pot ser nul");

        double total = 0;
        for (String ref : referencies) {
            // getOrDefault evita el desempaquetatge de null: es un if disfressat,
            // i es MILLOR que capturar el NullPointerException que provocaria get().
            int dies = diesRetard.getOrDefault(ref, 0);

            if (dies > 0) {
                double multa = Math.min(dies * TARIFA_DIARIA, MULTA_MAXIMA);
                total += multa;
            }
        }
        return total;
    }

    /**
     * Mitjana de multa per material. Separada del calcul total: cada metode, una cosa.
     * La llista buida es comprova explicitament: es un cas previsible, no una fallada.
     */
    public double calcularMultaMitjana(List<String> referencies, Map<String, Integer> diesRetard) {
        Objects.requireNonNull(referencies, "La llista de referencies no pot ser nulla");

        if (referencies.isEmpty()) {
            return 0.0;                 // mitjana d'un conjunt buit: 0 es la resposta correcta
        }
        return calcularMultaTotal(referencies, diesRetard) / referencies.size();
    }

    public static void main(String[] args) {
        CalculadoraMultes calc = new CalculadoraMultes();

        List<String> refs = List.of("LIB-0001", "LIB-0002", "LIB-0003", "LIB-0004");
        Map<String, Integer> retards = Map.of(
                "LIB-0001", 4,          // 1.00 EUR
                "LIB-0002", 0,          // sense retard
                "LIB-0003", 120);       // 30.00 -> limitat a 20.00
        // LIB-0004 no apareix al mapa: getOrDefault retorna 0

        System.out.printf("Total: %.2f EUR%n", calc.calcularMultaTotal(refs, retards));
        System.out.printf("Mitjana: %.2f EUR%n", calc.calcularMultaMitjana(refs, retards));
        System.out.printf("Mitjana de llista buida: %.2f EUR%n",
                calc.calcularMultaMitjana(List.of(), retards));
    }
}

Sortida:

Total: 21.00 EUR
Mitjana: 5.25 EUR
Mitjana de llista buida: 0.00 EUR

Canvis clau i la seva raó:

  1. Zero try-catch. El mètode no sap què fer amb una fallada, així que no en captura cap. És la millor decisió possible en molts mètodes de servei, i per això el catch que hi havia abans era un mer ritual.
  2. getOrDefault en comptes de capturar el NullPointerException. Comprovar és més barat, més clar i més ràpid que provocar i atrapar (apartat 14).
  3. Objects.requireNonNull per als arguments: fallada ràpida amb missatge útil, en comptes d'un NullPointerException opac tres línies més avall. Es desenvolupa a 06-03.
  4. La mitjana se separa del total i la llista buida es comprova explícitament: la divisió per zero deixa de ser possible.
  5. No hi ha println a dins: el càlcul no imprimeix. Presentació i lògica, separades (03-08).

Solució 3

package com.nexussoftware.bibliotech.servei;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Lectura de configuracio amb politica d'error PER PARAMETRE.
 *
 * La llico de disseny: no existeix "la forma correcta" de gestionar un error.
 * Depen de si el programa pot continuar sense aquesta dada.
 */
public class ConfiguracioBiblioTech {

    private final Map<String, String> propietats;

    private static final Set<String> CERTS  = Set.of("true", "si", "cert", "1", "s", "yes");
    private static final Set<String> FALSOS = Set.of("false", "no", "0", "n");

    public ConfiguracioBiblioTech(Map<String, String> propietats) {
        // Copia defensiva: ningu no modificara la configuracio per l'esquena (03-07)
        this.propietats = new HashMap<>(propietats);
    }

    /**
     * POLITICA A: parametre OBLIGATORI.
     * Sense ell, el programa no pot funcionar correctament: es llanca.
     * Es conserva la causa quan existeix, per no perdre el detall (06-03).
     */
    public int llegirEnterObligatori(String clau) {
        String valor = propietats.get(clau);

        if (valor == null) {
            throw new IllegalStateException(
                    "Falta el parametre obligatori '" + clau + "' a la configuracio");
        }
        try {
            return Integer.parseInt(valor.trim());
        } catch (NumberFormatException e) {
            // TRADUCCIO amb causa: qui crida no ha de saber que a dins
            // hi havia un parseInt, pero el stack trace conservara el detall.
            throw new IllegalStateException(
                    "El parametre obligatori '" + clau + "' ha de ser un enter, i val '"
                            + valor + "'", e);
        }
    }

    /**
     * POLITICA B: parametre OPCIONAL.
     * Hi ha un valor per defecte raonable: es recupera i s'informa.
     */
    public int llegirEnterOpcional(String clau, int perDefecte) {
        String valor = propietats.get(clau);

        if (valor == null) {
            System.out.println("  [config] '" + clau + "' absent. S'usa " + perDefecte);
            return perDefecte;
        }
        try {
            return Integer.parseInt(valor.trim());
        } catch (NumberFormatException e) {
            System.out.println("  [config] '" + clau + "' = '" + valor
                    + "' no es un enter. S'usa " + perDefecte);
            return perDefecte;
        }
    }

    /**
     * POLITICA C: valor per defecte AMB validacio de rang.
     * Distingeix dos motius de rebuig diferents, perque per diagnosticar
     * no es el mateix "has escrit lletres" que "has escrit un numero absurd".
     */
    public double llegirDecimalAmbRang(String clau, double min, double max, double perDefecte) {
        String valor = propietats.get(clau);

        if (valor == null) {
            System.out.println("  [config] '" + clau + "' absent. S'usa " + perDefecte);
            return perDefecte;
        }

        double convertit;
        try {
            convertit = Double.parseDouble(valor.trim().replace(',', '.'));
        } catch (NumberFormatException e) {
            System.out.println("  [config] '" + clau + "' = '" + valor
                    + "' no es un decimal. S'usa " + perDefecte);
            return perDefecte;
        }

        // La validacio de rang va FORA del try: no es una fallada de conversio,
        // i posar-la a dins faria que una futura fallada de l'if es confongues.
        if (convertit < min || convertit > max) {
            System.out.printf("  [config] '%s' = %.2f fora de [%.2f..%.2f]. S'usa %.2f%n",
                    clau, convertit, min, max, perDefecte);
            return perDefecte;
        }
        return convertit;
    }

    /**
     * POLITICA D: boolea tolerant.
     * No hi ha excepcio que capturar: no existeix cap Boolean.parseBoolean que falli
     * (retorna false davant de qualsevol cosa, que es justament el problema).
     * Es resol amb conjunts i validacio explicita.
     */
    public boolean llegirBoolea(String clau, boolean perDefecte) {
        String valor = propietats.get(clau);

        if (valor == null) {
            System.out.println("  [config] '" + clau + "' absent. S'usa " + perDefecte);
            return perDefecte;
        }

        String normalitzat = valor.trim().toLowerCase();
        if (CERTS.contains(normalitzat))  { return true; }
        if (FALSOS.contains(normalitzat)) { return false; }

        System.out.println("  [config] '" + clau + "' = '" + valor
                + "' no es un boolea reconeixible. S'usa " + perDefecte);
        return perDefecte;
    }

    // ------------------------------------------------------------------

    public static void main(String[] args) {
        Map<String, String> props = new HashMap<>();
        props.put("dies.prestec",   "15");
        props.put("tarifa.diaria",  "zero");        // invalid
        props.put("multa.maxima",   "999");         // fora de rang
        props.put("avisos.actius",  "SI");          // valid, en majuscules
        props.put("max.prestecs",   "3");
        // "llindar.lleu" deliberadament absent

        ConfiguracioBiblioTech config = new ConfiguracioBiblioTech(props);

        System.out.println("== Lectura de configuracio de BiblioTech ==");

        int dies = config.llegirEnterObligatori("dies.prestec");
        System.out.println("DIES_PRESTEC    = " + dies);

        double tarifa = config.llegirDecimalAmbRang("tarifa.diaria", 0.0, 5.0, 0.25);
        System.out.println("TARIFA_DIARIA   = " + tarifa);

        double maxima = config.llegirDecimalAmbRang("multa.maxima", 0.0, 100.0, 20.0);
        System.out.println("MULTA_MAXIMA    = " + maxima);

        int llindar = config.llegirEnterOpcional("llindar.lleu", 7);
        System.out.println("LLINDAR_LLEU    = " + llindar);

        boolean avisos = config.llegirBoolea("avisos.actius", false);
        System.out.println("AVISOS_ACTIUS   = " + avisos);

        // Demostracio de la politica A quan el parametre obligatori falta
        System.out.println("\n== Parametre obligatori absent ==");
        try {
            config.llegirEnterObligatori("port.servidor");
        } catch (IllegalStateException e) {
            System.out.println("ERROR DE CONFIGURACIO: " + e.getMessage());
            System.out.println("Causa subjacent: " + e.getCause());
        }

        // Demostracio de la politica A quan el valor es invalid: hi ha causa
        System.out.println("\n== Parametre obligatori invalid ==");
        try {
            config.llegirEnterObligatori("tarifa.diaria");
        } catch (IllegalStateException e) {
            System.out.println("ERROR DE CONFIGURACIO: " + e.getMessage());
            System.out.println("Causa subjacent: " + e.getCause());
        }
    }
}

Sortida:

== Lectura de configuracio de BiblioTech ==
DIES_PRESTEC    = 15
  [config] 'tarifa.diaria' = 'zero' no es un decimal. S'usa 0.25
TARIFA_DIARIA   = 0.25
  [config] 'multa.maxima' = 999.00 fora de [0.00..100.00]. S'usa 20.00
MULTA_MAXIMA    = 20.0
  [config] 'llindar.lleu' absent. S'usa 7
LLINDAR_LLEU    = 7
AVISOS_ACTIUS   = true

== Parametre obligatori absent ==
ERROR DE CONFIGURACIO: Falta el parametre obligatori 'port.servidor' a la configuracio
Causa subjacent: null

== Parametre obligatori invalid ==
ERROR DE CONFIGURACIO: El parametre obligatori 'tarifa.diaria' ha de ser un enter, i val 'zero'
Causa subjacent: java.lang.NumberFormatException: For input string: "zero"

El que demostra l'exercici:

  1. La mateixa excepció, quatre polítiques diferents. NumberFormatException es converteix en un IllegalStateException que avorta l'arrencada, o en un avís amb valor per defecte, segons com de crític sigui el paràmetre. La política la marca el context, no el tipus d'excepció.
  2. La causa es conserva en traduir (l'últim bloc de sortida), i es perd completament quan no hi havia excepció original (el cas del paràmetre absent, amb Causa subjacent: null). Aquesta diferència es veu al stack trace com la presència o absència de Caused by:.
  3. No tot error necessita una excepció. llegirBoolea no captura res: fa servir conjunts i validació explícita, perquè Boolean.parseBoolean no llança —simplement retorna false davant de qualsevol brossa, que és una fallada silenciosa pitjor que una excepció.

Conclusió

Ja saps gestionar una excepció, no només llegir-la. Domines la sintaxi i la semàntica exactes de try-catch: què es protegeix —tot el bloc, inclosos els mètodes que es cridin des d'ell, per profunds que siguin—, què s'abandona quan salta l'excepció —tot el que quedi del try, bucles inclosos— i què passa si cap catch no és compatible: l'excepció continua pujant com si el try no existís. I saps que la posició del try respecte a un bucle és una decisió de disseny amb dues semàntiques diferents: fora per a "tot o res", dins per tolerar element a element.

Coneixes l'objecte capturat i els seus mètodes: getMessage —que pot ser null—, toString, getStackTrace per inspeccionar els marcs, getCause per baixar a la causa arrel i getSuppressed, que prendrà sentit a 06-06. I saps que printStackTrace() no és gestió d'errors, sinó una eina de depuració local que a 06-07 substituiràs per un Logger.

Tens la regla d'ordre dels múltiples catch —de la subclasse a la superclasse, amb l'error de compilació exception X has already been caught si la inverteixes— i saps que NumberFormatException descendeix d'IllegalArgumentException, un parentiu que trenca més d'un ordre aparentment correcte. Manegues el multi-catch amb | i les seves dues regles: els tipus no poden estar emparentats per herència, i la variable és implícitament final. Saps quan imbricar try —dos àmbits de recuperació diferents— i quan extreure un mètode al seu lloc. I coneixes l'abast de les variables del try i l'anàlisi d'assignació definitiva que obliga a assignar també al catch, o a sortir del mètode.

El més important: saps què es pot fer dins d'un catch. Recuperar-se amb un valor per defecte que sigui una resposta legítima i no una tapadora; reintentar amb límit, espera creixent i fallada definitiva en esgotar-los, i només quan la fallada depèn de l'entorn i no de les dades; traduir al vocabulari de la teva capa conservant la causa; i registrar i rellançar amb throw e; quan la decisió correspon a algú de dalt, sense caure en el log duplicat. Un catch que no fa cap de les quatre és un catch mal posat.

I tens la demostració dels antipatrons: el catch buit, que fa que un importador processi 3 exemplars en comptes de 6 sense deixar el més mínim rastre i que és literalment pitjor que no capturar res; el catch (Exception e) massa ample, que converteix els teus propis bugs en missatges tranquil·litzadors per a l'usuari; i les excepcions com a flux de control, amb els números que les condemnen: unes 70 vegades més lent que un if, amb fillInStackTrace com a responsable —i amb el contrast revelador que una excepció sense stack trace amb prou feines costa un 50% més que l'if, cosa que prova que el mecanisme de throw/catch en si és barat i que les excepcions per al que és excepcional no tenen cost apreciable.

BiblioTech ha guanyat la seva primera defensa real: MenuBiblioTech ja no mor quan Marta Ruiz escriu "dotze". L'opció del menú es reintenta indefinidament perquè hi ha una persona davant; les dades numèriques es reintenten amb límit i cancel·len l'operació sense tocar l'estat; la tarifa accepta el valor per defecte només prement Enter. I tot això capturant exclusivament NumberFormatException, de manera que un bug real continuaria sortint a la llum en comptes de disfressar-se d'"entrada invàlida".

Però la capa de servei continua mentint. Cataleg.registrar continua retornant el seu false mut, cercarPerReferencia continua retornant null, i els constructors del mòdul 3 continuen corregint les dades invàlides amb avisos per consola en comptes de rebutjar-les. Capturar no n'hi ha prou: cal assenyalar la fallada en el moment en què es detecta.

Això és la lliçó següent, Throw i throws: com llançar una excepció explícitament amb throw i per què el codi posterior és inabastable; com declarar-la amb throws i què obliga això a qui crida; la validació d'arguments amb IllegalArgumentException, IllegalStateException i Objects.requireNonNull, que per fi jubilarà els avisos per consola dels constructors del mòdul 3; el principi de fallar ràpid; l'encadenament d'excepcions amb la causa —i la demostració exacta de la informació que es perd si no la conserves—; la traducció entre capes perquè la capa de servei no filtri les excepcions de la capa de dades; i les regles de throws en la sobreescriptura de mètodes. En acabar-la, Cataleg.cercarPerReferencia deixarà de retornar null per sempre.

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