Enrera
Mòdul 3
Iniciació a la programació en Java
  Pràctica
1
2
3
4
   
Exercicis
Exercicis
 
 
  Quan alguna cosa no va bé: la gestió d'excepcions
   
  Què són les excepcions?
 

Si ens permeteu el tòpic, quan es fa disseny de programari, si una cosa pot anar malament segur que anirà malament: un usuari teclejarà malament una informació, un fitxer desapareixerà màgicament del nostre ordinador, el nostre servidor estarà apagat quan intentem operar amb ell... Com que la casuística d'errors potencials és pràcticament inabastable, Java disposa d'un concepte particular, present a gairebé tots els llenguatges més moderns: el concepte d'excepció.

Com tos els elements de Java, una excepció és un objecte que fa una tasca concreta: esperar que es produeixin errors o condicions estranyes en el programa. Si passa alguna eventualitat l'excepció ens avisa i ens ajuda a reconduir el problema tot intentant recuperar el funcionament normal de la nostra aplicació. Tot objecte excepció deriva de la classe java.lang.Throwable. Aquesta classe a més de la subclasse java.lang.Exception que ara estudiarem, en té una altra, la subclasse java.lang.Error. Si ens salta un error no hi ha res a fer: es tracta d'un problema del qual no ens en podrem recuperar i el programa s'aturarà. En canvi, si es produeix una excepció (exception) llavors podrem refer-nos-en.

El funcionament és força senzill: el programador protegeix un bloc de codi amb la declaració d'una excepció. Si en l'execució d'aquell tros de programa es produeix un error, l'objecte excepció el captura i fa fluir el programa cap un fragment de codi on s'intentarà corregir l'esmentat error. Un cop corregit, intentarem retornar on estàvem i continuar amb normalitat.

Què passa si no protegim el codi amb declaracions d'excepcions? El programa sempre intentarà trobar un gestor d'excepcions i si no el troba al lloc on s'ha produït, el buscarà al lloc des d'on hem cridat el mètode o la classe i així anirà pujant fins arribar a la Màquina Virtual de Java, la qual es limitarà a tancar el programa. De cara a l'usuari, un programa acabat així és un programa "trencat", sinònim de poca qualitat. Hem d'evitar que els errors arribin a la Màquina Virtual si no volem que ens facin quedar malament.

   
  Planificant l'estratègia de captura d'errors
   
 

Davant d'una excepció podem actuar bàsicament de dues formes:

  • Atacar immediatament el problema i solucionar-lo quan es presenta, utilitzant un bloc try-catch.
  • Passar el problema al mètode que crida al codi que falla, tot indicant-li que s'ha produït una excepció. En aquest cas, serà el mètode qui crida a qui ha de solucionar els problemes. Si seguim aquesta segona estratègia hem de fer una d'aquestes dues coses:
    • Llençar nosaltres l'excepció mitjançant la clàusula throw. Per exemple,

      if (num<0) throw new ArithmeticException();

      O bé,

    • No preveure res i definir el nostre mètode com a lloc potencial d'errors de diferents categories, tot posant la clàusula throws <Un_Tipus_d_Excepcio> a la definició del mètode. Com a exemple d'un possible mètode amb aquest tractement tenim:

      public void llegeixFitxer() throws IOException {
          ...
          ...
          ...
      }
 

El bloc try-catch

   
 

En termes generals, si som capaços de preveure un problema potencial, hem d'intentar anticipar-nos-hi i donar-ne també la solució. El mètode més agraït de fer-ho és a través de l'estructura de control try-catch:

try{
    <codi a protegir>;
} catch (Exception e) {
    <què fem en cas d'error>;
}

La lògica d'aquesta estructura és la següent: si es produeix qualsevol error a l'interior del bloc try{ }, es crea un objecte excepció -Exception e al nostre exemple- i el flux del programa salta dins del bloc catch{ } on hi haurem posat la solució del problema.

En situacions molt elementals, les més freqüents, no ens caldrà ni recórrer al propi objecte Exception. Aquest és un cas ordinari de protecció i solució amb l'estructura try...catch:

int nombre;
String cadena;

    try {
        nombre = Integer.parseInt(cadena);
    } catch( Exception e) {
        nombre = -1;
    }

Si intentem fer una conversió d'una cadena cadena a nombre enter (int), podem tenir problemes amb facilitat: que la cadena estigui buida o en estat null, que el seu contingut estigui format per lletres no traduïbles a nombre enter, etc. Amb una estructura com l'anterior, ens assegurem que, en cas d'error, nombre prendrà un valor i llavors el programa no es trencarà.

Si a més vull obtenir detalls de l'error, (cosa que em pot servir per depurar el programa, per exemple) l'objecte Exception em donarà aquests detalls:

int nombre;
String cadena;

try {
    nombre = Integer.parseInt(cadena);
} catch (Exception e) {
    System.out.println(e.getMessage());
    nombre = -1;
}

El mètode e.getMessage() (de la classe java.lang.Throwable, classe mare de java.lang.Exception) ens retorna una cadena amb la descripció de l'error que acabem de tenir. Si el fem sortir per la pantalla, podem localitzar fàcilment on ens està fallant el programa.

A la pràctica anterior hem construït un codi d'aquest tipus i l'hem vist treballar!

En moltes ocasions ens resultarà útil concretar quin tipus d'excepció s'està produint o ens podrà interessar fer coses diferents segons el tipus d'excepció que s'hagi generat. Per a la gestió d'aquestes diferències no n'hi ha prou amb recollir una excepció genèrica, una instància de la classe java.lang.Exception. Haurem d'utilitzar alguna de les subclasses d'excepció predefinides al SDK de Java, o bé en podrem crear d'altres al nostre gust.

Disposem d'una àmplia jerarquia d'excepcions. Visiteu la documentació de la classe java.lang.Exception i les trobareu totes definides. Aquí us en llistem algunes de les més usuals:

ArithmeticException Per exemple, quan fem una divisió per 0
NullPointerException Quan ens referim a un objecte null, no inicialitzat
ClassCastException Error de conversió (casting) d'una classe a una altra
ClassNotFoundException Quan la classe que cridem no existeix
NoSuchMethodException Quan cridem un mètode inexistent en una classe
IOException Quan es produceix un error d'entrada - sortida
SQLException Quan es produeix un error d'SQL en consultes a bases de dades
   
  Observeu ara com podem reaccionar de manera diferent segons la categoria de l'error:
   

/*
* Programa per a la gestió d'errors aritmètics i errors genèrics
* --------------------------------------------------------------
*/
class errorsAritmetics {

    public static void main( String args[]) {
            try {
                int nombre=0;
                String[] matriu = new String[3];

                
// ----------- Una divisió per zero --------
                nombre /= 0;
                System.out.println(nombre);

                
// ----------- Desbordament d'una matriu ---
                matriu[4]="Element de la matriu";
                System.out.println(matriu[4]);
            } catch (ArithmeticException e) {
                System.out.println( "Atenció, divisió per zero!" );
            } catch( Exception e ) {
                System.out.println( "S'ha produït un error!" );
            }
    }

}

Si executem el programa tal com està, quan intentem dividir un nombre per zero es generarà una excepció ArithmeticException. La instrucció del programa davant d'un error d'aquest tipus és escriure en pantalla "Atenció, divisió per zero". Si comentem la línia "nombre /=0" i tornem a compilar i executar el programa, estarem creant una excepció de desbordament de matriu. Això passarà el control al bloc catch de gestió genèrica d'excepcions i obtindrem el missatge "S'ha produït un error!".

   
  Creem les nostres pròpies excepcions!
   

També, si ens interessa, podem crear les nostres pròpies excepcions, només ens cal extendre la classe java.lang.Exception.

Imaginem el problema següent: el nostre director ens demana un programa que assigni alumnes a les aules i hem de vigilar que una aula no tingui mai més d'un nombre determinat d'alumnes. Com que aquesta situació de control es donarà força vegades, creem una excepció per a expressar-la:

   

Escrivim aquest petit programa de construicció de l'excepció (en el capítol següent aprendrem a fer paquets (packages) i les classes amb totes les excepcions personalitzades d'un programa es poden posar en un paquet).

public class MassaAlumnesException extends Exception {
    public MassaAlumnesException(String s) {
        super(s);
    }
}

Després, escrivim aquest programa de verificació d'ocupació d'aules

import java.io.*;


/*
* Programa per al control del nombre d'alumnes
*---------------------------------------------------------------
*/

public class MassaAlumnes {

    public static void main(String[] args) throws IOException {
        BufferedReader entrada = new BufferedReader(
                                 new InputStreamReader(System.in));
        System.out.println("No. d'alumnes de la classe?");
        String nombrealumnes = entrada.readLine();
        int num=0;
            try {
                num=Integer.parseInt(nombrealumnes);
                    if (num>30) throw
                            new MassaAlumnesException("> de 30!");
                System.out.println("Nombre acceptat");
            } catch (MassaAlumnesException m) {
                System.out.println(m.getMessage());
            } catch (Exception e) {
                System.out.println("Error d'entrada de dades");
            }
    }
}

Hem creat una entrada de dades des del teclat on se'ns demana el nombre d'alumnes que ocuparan l'aula. Si intentem entrar un nombre superior a 30, el programa genera una excepció MassaAlumnesException:

if (num>30) throw new MassaAlumnesException("Més de 30!");

Però no només la genera sinó que, en lloc de deixar-la pujar cap a la Màquina Virtual, la captura en el bloc try-catch i la gestiona: fa sortir per pantalla el missatge descriptor de l'excepció.

Observeu que si errem en l'entrada de teclat -si escrivim lletres en lloc de nombres- també estem protegits gràcies a una excepció genèrica que, mitjançant el missatge "Error d'entrada de dades", ens informarà de l'error.

   
  "throw" en el codi del mètode i "throws" a la declaració del mètode
 
 

Anterior ja hem vist com conviuen els diferents mètodes de gestió d'excepcions: hem utilitzat blocs try-catch per reaccionar davant problemes previsibles i hem utilitzat sentències "trow" i "throws".

Endevineu la utilització d'ambdues fòrmules un cop posades al programa?

  • La clàusula throw ens serveix als programadors per decidir en quin moment ha de saltar una excepció. De fet la creem, la llancem nosaltres. Sona una mica estrany que siguem nosaltres els que provoquem els errors, però això és útil en moltes ocasions. Molts projectes de Java fonamenten una part important del control del flux en la generació d'errors.

  • La clàusula throws, en canvi, és una defensa genèrica que nosaltres no provoquem. En el nostre programa l'hem posada a la declaració del mètode main(). És on l'hem de posar sempre: a les declaracions. Podem protegir-nos de tantes famílies d'excepcions com ens interessi: a la declaració només hem de separar-les per comes (","):

    public static void main(String[] args) throws
           IOException,ClassNotFoundException,ArithmeticException {
        ...
        ...

    Una declaració així informa que el mètode en qüestió és susceptible de tenir errors d'entrada-sortida, d'inexistència de classes i aritmètics. Qui vagi per damunt d'aquest mètode, s'encarregarà de fer alguna cosa si salta una excepció.

    Heu de pensar, però, que hem de ser coherents amb els throws que definim en els nostres programes
  • Si escrivim un programa que faci servir un mètode auxiliar protegit genèricament amb un o diversos throws, hem de preveure en el nostre propi codi reaccions davant les excepcions que ens pot enviar aquest mètode auxiliar. Per tant, o bé tanquem la crida al mètode auxiliar amb un bloc try-catch o bé a la definició del nostre propi mètode també haurem de definir-lo com a productor d'excepcions del mateix tipus que el mètode auxiliar. Amb això esperem que l'excepció es resolgui en una part més alta del nostre programa: en algun lloc o altre hem de solucionar el problema o al final se'l menjarà la Màquina de Java i es trencarà el programa

 

 

Finalment, demostrarem la utilitat de les excepcions en una situació de control de flux de programa.

   
Control de la sortida del programa a través d'una excepció:
   
 

Ens interessa que el nostre programa d'ocupació d'aules vagi demanant dades indefinidament fins que l'usuari digui prou. Amb la combinació d'un cicle indefinit i un gestor d'errors podem solucionar el problema.

Escrivim aquesta versió modificada del programa MassaAlumnes.java:

 
import java.io.*;

/*
* Programa per al control del nombre d'alumnes
*---------------------------------------------------------------
*/

public class MassaAlumnes_2 {

    public static void main(String[] args) throws IOException {
        int n=0;
            try {
                    for (;;) {
                        BufferedReader entrada=new BufferedReader(
                                new InputStreamReader(System.in));
                        System.out.println(
                                   "No. d'alumnes de la classe? "+
                                   "(\"Prou\" per acabar)");
                        String numAlumnes=entrada.readLine();
                        int num=0;
                            try {
                                num=Integer.parseInt(numAlumnes);
                                    if (num>30) throw  
                                        new MassaAlumnesException(
                                                      "> de 30!");
                                        n++;
                                        System.out.println(
                                                  "No. acceptat");
                            } catch (MassaAlumnesException m) {
                                System.out.println(m.getMessage());
                            }
                    }
            } catch(Exception e) {
                System.out.println("heu situat "+n+" aules");
            }
    }

}
   
 

El cor del programa és la sentència for (;;;): És un cicle indefinit. S'anirà reproduint fins que fem saltar algun error que no sigui MassaAlumnesException. Si és d'aquest tipus la gestió de l'excepció es queda dins del cicle i no passa res. Si no ho és, l'excepció es propaga més enllà del cicle for (;;;) i s'acaba el programa. (for (;;;) és equivalent a while(true)).

El resultat és que podem anar entrant dades indefinidament . Si assignem massa alumnes a una aula el programa ens avisa però continua treballant. Si l'error no és MassaAlumnesException, el programa ens informa del número d'aules que hem situat correctament i surt. Qualsevol entrada de teclat que no sigui un nombre enter produirà la sortida del programa:

 
   
   
 
Amunt