![]() |
Pràctica 4: Excepcions ![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Si has arribat fins aquí en aquest curs de programació ja deus haver experimentat que si una cosa pot funcionar malament, en algun moment o altre acabarà funcionant malament. No falla. En un entorn informàtic els errors són inevitables. No només perquè els comet el programador quan escriu el seu codi, sinó també per les condicions de l'entorn d'execució: un disc es pot omplir, un servidor pot caure i no contestar-nos, un motor de dades pot fallar... Per a conviure amb aquests errors i, fins i tot, posar-los a favor del programador, Java ha creat un conjunt de classes especials, les excepcions, que fan possible que el programa intercepti els errors i pugui reaccionar per a solucionar-los. Una excepció és una instància d'un tipus especial de classe que hereta indirectament de la classe Throwable, una classe prou important com per estar al paquet java.lang, aquell que no cal importar mai. Aquest és l'arbre d'herència de les excepcions. Totes deriven de Throwable. Algunes deriven de la classe Exception i d'altres de la classe RuntimeException. |
||
![]() |
||
Esquema d'herència de les excepcions |
||
Fent saltar una excepció |
||
|
||
![]() |
Inicia un nou projecte i posa-li el nom d'"excepcions". El faràs servir per escriure les diferents classes d'aquesta pràctica. Escriuràs una classe extremadament simple, la classe DividirPerZero la feina de la qual és equivocar-se i provocar un error: intentar dividir un número per zero. Com ja deus saber, una divisió per zero té un valor infinit. A Java no li senten bé els infinits, no sap que fer-ne i, si es dóna una situació d'aquest tipus, es produirà un error en temps d'execució. Comprova-ho: |
|
![]() |
/** * Excepcions: dividir per zero * * @author Angel Solans * @version 25-02-2005 */ public class DividirPerZero { public int divideixPerZero(int unEnter) { |
|
Un cop tinguis escrita la classe, compil·la i crea'n un objecte. Intenta executar el mètode divideixPerZero(). En quan has introduït el número enter que ha de fer de numerador, el programa s'interromp i salta una finestra com aquesta, que ens indica que s'ha produït un errord d'aritmètica (ArithmeticException) a l'intentar fer una divisió per zero (/ by zero). |
||
![]() |
||
El BlueJ fent saltar una excepció |
||
En aquest moment, l'aplicació ha deixat de funcionar. Imagina't quin problema: un programa que es trenca per un error tan petit! El que faràs ara serà protegir aquesta font d'errors gestionant l'excepció que s'ha produït . Modifica la classe DividirPerZero de la següent forma: |
||
![]() |
/** * Excepcions: dividir per zero * * @author Angel Solans * @version 25-02-2005 */ public class DividirPerZero { public int divideixPerZero(int unEnter) { |
|
Observa les modificacions respecte la classe original: has embolcallat el codi que pot donar error amb una estructura del tipus try {} catch {}, que és una de les formes per a gestionar excepcions de les que disposa Java. En pseudocodi el text que has afegit al programa es pot traduir com: "intenta fer la divisió d' unEnter per zero . Si no hi ha cap error, retorna el valor de la divisió. Compil·la novament la classe i executa-la. Crea una nova instància de la classe DividirPerZero i executa el mètode divideixPerZero(). El programa ja no es trenca! En el moment que es produeix l'error, el fluxe de programa abandona el bloc try {} i salta cap al bloc catch {}. La informació de l'error la porta l'excepció e: |
||
![]() |
||
Observa com el programa obre una cònsola i t' informa de l'error: |
||
![]() |
||
i, finalment, retorna el valor -1 :
|
||
![]() |
||
La gestió de l'excepció ha fet possible recuperar-te de l'error sense que el programa es trenqui. |
||
Diferents tipus d'Excepcions |
||
El primer que has de saber és que, d'excepcions, n'hi ha de molts tipus. Moltes estan predefinides pel llenguatge, altres les pots crear tu. El paquet java.lang en defineix un grup d'utilització comuna. Aquestes excepcions salten freqüentment, especialment quan no fem les coses massa bé: NullPointerException, ArrayIndexOutOfBoundsException, etc. són excepcions que t'avisen de que estàs accedint als mètodes d'un objecte null, que t'has passat de rang en la lectura d'una matriu i aquest tipus de repèl habituals en el procés de programació d'un projecte. Si, per exemple, estàs llegint una matriu i intentes fer una lectura fora de rang, salta una excepció en temps d'execució: |
||
![]() |
||
El BlueJ ens informa d'una excepció del tipus ArrayIndexOutOfBoundsException |
||
En aquest exemple hem intentat llegir la sisena posició d'una matriu que només té tinc elements. El programa genera una excepció de les que estan 'enllaunades' en el llenguatge, una ArrayIndexOutBoundsException. Ara és responsabilitat del programador decidir com es captura l'excepció i que s'ha de fer per recuperar el normal funcionament del programa. No t'explicarem aquí tota la jerarquia d'excepcions predefinides. Només et proposem un petit exercici. Intenta endevinar, pel nom, en quin context es produeixen les següents excepcions del paquet java.lang: StringIndexOutOfBoundsException NumberFormatException ClassCastException OutOfMemoryException
Llençar una excepció predefinida a voluntat del programador Aquesta classe, per exemple, genera una excepció si, al cridar el constructor, li donem un valor null al paràmetre nick:
|
||
![]() |
/** * * @author Angel Solans * @version 25-02-2005 */ public class Jugador { private String nick; public Jugador(String nick) throws Exception { if (nick==null) throw new Exception("S'ha de donar un nick al jugador"); this.nick=nick; } } |
|
Escriu aquesta nova classe, compil·la i crea'n un objecte. En el moment de crear-lo, el BlueJ et demanarà un valor per al paràmetre nick. Si escrius null, el programa es trencarà tot donant aquest error: |
||
![]() |
||
El BlueJ es trenca per una excepció que hem llençat nosaltres des del programa |
||
Ja has disparat una excepció a la teva voluntat. Aquesta eina et pot ser força útil en temps de disseny i depuració de programes i també per a fer més robust i legible el codi que tinguis en producció. El mecanisme per a provocar l'excepció ha estat una crida a throw new Exception. En aquesta pràctica t'explicarem millor com l'has d'utilitzar. |
||
Gestió completa d'excepcions: els blocs try-catch. | ||
Fixa't en la forma de gestionar l'error que et proposem en el text de la primera classe de la pràctica, DividirPerZero, i compara-la amb la forma en què has operat a la classe Jugador. En un cas has utilitzat un bloc try-catch i en el següent has fet anar una sentència throws. Els blocs try-catch constitueixen el que definim com a mètode de gestió completa d'excepcions en Java. Diem que són el mètode complet perquè quan tanquem un bloc de codi en un try-catch no només protegim el programa en el bloc try sinó que també decidim el mecanisme de recuperació en el bloc catch . Aquesta és la sintaxi d'un bloc try-catch: try{ 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. Per exemple: try{ Podem posar vàries sentències catch per cada try{. Per exemple aquesta protecció és perfectament correcta: try{ El block try pot contenir opcionalment un tercer component. És la cláusula finally. Serveix per a que, passi el que passi amb la gestió de l'excepció, salti o no salti, s'executi un codi. Té el següent aspecte: try { Finally fa possible que un bloc try no contingui cap clàusula catch. Això és legal: try {
|
||
Gestió parcial d'excepcions: throw i throws. | ||
Si no protegim el nostre codi amb blocs try-catch els errors es propagen en cascada saltant entre mètodes i classes fins arrivar a la màquina virtual que és quan el programa es trenca. Ja ho has experimentat al començar la pràctica. Imagina un programa hipotètic que tingués el següent aspecte: |
||
![]() |
||
La màquina de java crida el mètode main() del programa. Aquest fa una crida a un primer mètode i el primer mètode posa a treballar un segon mètode. Si es produeix un error en el segon mètode i no hi ha cap bloc try-catch protector es genera una excepció que el segon mètode passa al primer. Si aquest primer mètode no està preparat per aturar l'excepció i no sap que fer-ne, li passa la patata calenta al mètode main(). Finalment si aquest mètode tampoc sap que fer, li cedeix l'excepció a la màquina virtual i es trenca el programa. A vegades no ens interessa tancar completament l'excepció i ens pot resultar útil que l'error es propagui controladament d'un mètode a un altre. Això ho podem aconseguir amb l'especificació d'excepcions throws en combinació amb el generador d'excepcions throw. Entre els dos formen un tàndem que ens permet ordenar perfectament el fluxe del programa en la gestió dels errors. |
||
![]() |
Crea una nova classe en el projecte Excepcions que porti de nom "ExpenedorEntrades". Ha de tenir el següent contingut: |
|
![]() |
/** * * @author Angel Solans * @version 15-02-2005 */ public class ExpenedorEntrades { public boolean ven(int numentrades) throws Exception { if (numentrades<0 || numentrades>10) throw new Exception("Número d'entrades no permés"); return true; } public void guixeta(int numentrades) { |
|
Es tracta d'una simulació de venda d'entrades. El mètode guixeta() dóna la benvinguda a l'usuari i crida un segon mètode ven() que ens confirma la venda. Observa com planifiquem la gestió d'excepcions: en cas que intentem vendre menys d'una entrada o més de deu, farem saltar una excepció genèrica del tipus Exception. Com ens interessa avisar a qualsevol altre mètode que pugui utilitzar el mètode ven() que del seu interior pot saltar una excepció del tipus Exception, ho publiquem amb la clàusula throws Exception. La clàusula throws serveix, doncs, per a avisar a qualsevol mètode client que és força probable que el mètode retorni una excepció del tipus indicat en el throws. I si es fa aquest avís és perquè el mètode en qüestió no pensa fer cap gestió d'aquesta excepció. És el mètode client qui ha de preveure que en farà si es dóna el cas. Intenta compil·lar la classe. Què passa? Doncs que falla la compil·lació i et retornen el següent missatge d'error: |
||
unreported exception java.lang.Exception; must be caught or declared to be thrown | ||
Què és el que ha passat? Java t'està protegint i està intentant que gestionis correctament l'excepció. Les excepcions del tipus genèric Exception són d'una categoria tal que el Java no et deixarà que es propaguin fins a la màquina virtual de Java. Si en un mètode declares que pot saltar una Exception a través d'un throws a la capçalera, el mètode client ha de contemplar forçosament la gestió de l'excepció, ja sigui a través d'un bloc try-catch o posant un throws a la capçalera del client. Modifica el programa de la següent forma: |
||
![]() |
/** * * @author Angel Solans * @version 15-02-2005 */ public class ExpenedorEntrades { public boolean ven(int numentrades) throws Exception { if (numentrades<0 || numentrades>10) throw new Exception("Número d'entrades no permés"); return true; } public void guixeta(int numentrades) { |
|
Observa que el que has fet és tancar el mètode potencialment perillós amb un bloc try-catch. Només amb això, el compil·lador ja no es queixarà perquè has completat la protecció davant l'excepció. Compil·la el programa. Ara no tindràs cap problema. Crea una instància i executa el mètode guixeta(). Intenta la venda de diferents quantitats d'entrades. Observa què passa si vens menys d'una entrada o més de deu. Quan utilitzis la parella throws-throw observaràs que el compil·lador no sempre falla quan poses un throw en un mètode i no especifiquis el throws corresponent a la capçalera. Amb algunes excepcions protesta i amb altres no. Potser en un principi et semblarà una mica atzarós, però el llenguatge ho té completament determinat: Totes les excepcions derivades d'Exception s'han de gestionar íntegrament. En canvi totes aquelles que hereten de RuntimeException es poden gestionar de forma incompleta. L'esquema és el següent: |
||
|
||
![]() |
||
Java ha planificat les excepcions del tipus Exception per aquelles situacions en que l'error és previsible (operacions de lectura/escriptura, connexions a base de dades, etc) i, per tant, el cicle de control ha de ser complet. Les RuntimeException, en canvi, es produeixen en situacions imprevisibles (accés a objectes nuls, lectura de matrius fora de rang, divisions per zero, etc) habitualment producte d'un mal disseny de programa i són de control més relaxat. Java suposa que el programador depurarà el codi d'aquest tipus d'errors en el cicle de programació i no apareixeran quan entregui el programa al client. Has de tenir molt en consideració aquests dos tipus d'excepcions que t'ofereix el llenguatge, especialment en el moment en que creis les teves pròpies excepcions. Si vols que una excepció teva tingui una gestió estricta, fes-la derivar d'Exception. Si no, en tens prou amb que sigui filla de RuntimeException. Les regles per triar un o altre model no són estrictes, dependran una mica de les teves manies com a programador. Ara aprendràs a crear excepcions adaptades a les teves necessitats. |
||
|
||
Excepcions a la carta: Creant les nostres pròpies excepcions. | ||
Quan no en tinguis prou amb les excepcions convencionals sempre pots recórrer a la creació personalitzada d'excepcions. Si vols fer que es gestioni completament, fes-la derivar d'Exception si no, com has vist, la pots fer derivar de RuntimeException. Potser et preguntaràs per a què necessites crear excepcions personalitzades quan n'hi ha tantes d'enllaunades amb el llenguatge. La resposta està en l'augmen d'eficàcia en el control de les teves aplicacions. Imagina, per exemple, que fas un programa per a la gestió d'envasat de pastisseria industrial. No seria pràctic recollir i gestionar excepcions que contenen en el seu propi nom una bona informació sobre els errors que s'estan produïnt? Posa imaginació i dedueix que està passant si salten aquestes excepcions personalitzades en el teu programa d'envasat: ImpossibleTancarEnvasException MassaBrioxosALaCapsaException EnvasBuidException CruasantTrencatException Aquest és l'objectiu de les excepcions personalitzades, donar molta informació i permetre una gestió molt fina dels possibles errors que es poden donar en un programa. La creació d'una excepció personalitzada és una tasca força simple. En el teu projecte Excepcions crearàs una excepció personalitzada per a utilitzar en la cadena d'envasat de pastisseria industrial. És aquesta: |
||
![]() |
/** // Codi del treballador que ha trencat el cruasant public CruasantTrencatException(String codiEnvasador) { public String getCodiEnvasador() { public String toString() { } |
|
És tracta d'una excepció pensada per a saltar quan un treballador especialment inhàbil de la cadena d'envasat trenca un cruasant. Com el gerent de l'empresa et demana informació sobre el responsable de la malifeta, aprofites l'excepció per a incloure informació sobre l'obrer. Observa que hem triat Exception com a classe mare. Això vol dir que has de fer una gestió completa de l'excepció o no podràs compil·lar el programa. L'excepció té un atribut, codiEnvasador, que serveix per a recollir el codi del treballador responsable de l'error, i un mètode getter per accedir a l'atribut. Finalment, creem un mètode toString() amb informació relativa a l'origen de l'excepció. Escriu aquest programa per a posar en marxa la teva excepció personalitzada. Crea la classe Cadena al teu projecte Excepcions. Ha de ser així: |
||
![]() |
/** * * @author Angel Solans * @version 06-03-2005 */ public class Cadena { public void envasa(String envasador) throws CruasantTrencatException { if (envasador.equals("Manscompeus")) throw new CruasantTrencatException(envasador); System.out.println(envasador+" ha envasat el cruasant"); } public void cadenaEnvasat() { // Hem de protegir amb try-catch per força! try { envasa("Envasador Potent "); envasa("Envasador Mitjanet "); envasa("Manscompeus"); }catch(CruasantTrencatException e) { System.out.println( "Cruasant trencat per "+e.getCodiEnvasador()); } } } |
|
Crea'n una instància i executa el mètode cadenaEnvasat(). Analitza el funcionament de la teva primera excepció personalitzada. Quina feina ha fet? Et proposem completar aquesta pràctica creant un programa per a fer divisions que utilitza les excepcions per a fer el control d'entrada de dades. Necessites crear dues excepcions personalitzades i la classe que far les divisions. Afegeix-les totes al teu projecte: |
||
![]() |
/** * Excepció per a números que estan fora del rang. * * @author Angel Solans * @version 25-02-2005 */ public class ForadeRangException extends RuntimeException { public ForadeRangException(String msg) { super(msg); } } |
|
![]() |
/** * Excepció per a denominador igual a zero. * * @author Angel Solans * @version 25-02-2005 */ public class DenominadorZeroException extends RuntimeException { public DenominadorZeroException(String msg) { super(msg); } } |
|
![]() |
/** * * @author Angel Solans * @version 25-02-2005 */ public class Dividir { public void divideix(String num, String den) { double numerador=0.0; double denominador=0.0; String incorrecte="El càlcul no ha estat possible: "; try { |
|
Observa les excepcions personalitzades. En aquesta ocasió hem optat per a fer-les el més simples possible. Només contenen un constructor que activa el constructor de la classe mare. És el mecanisme més convencional de creació d'excepcions i suficient per a la major part dels casos. Repassa la classe Dividir. Veuràs que el sistema d'excepcions s'utilitza per a fer una validació completa del numerador i el denominador. Un mètode dinsRang() fa la verificació de la qualitat de dades. En el cas que les dades no siguin correctes, retorna una excepció del tipus ForadeRangException o DenominadorZeroException. |
||
![]() |
1) Observa la següent sequècia de blocs catch. L'ordre en que estan posats els elements és rellevant o indiferent? Raona la teva resposta. 2) Inventa una nova restricció de dades de la teva elecció. Crea una excepció personalitzada que la representi i afegeix-la a l'estructura de control de l'aplicació. |
|