Devi aver chiaro il funzionamento delle eccezioni e la loro gestione.
Il "lancio" (in altri linguaggi detto anche "sollevamento") dell'eccezione avviene quando si raggiunge uno stato di inconsistenza o di errore che potrebbe non permettere la prosecuzione del programma (ci sono programmatori che usano eccezioni con molta più disinvoltura, non è in generale una buona pratica abusarne...).
L'eccezione una volta lanciata risale i contesti (lo stack di chiamate) fino a quando non trova un blocco try/catch che la gestisce.
L'eccezione viene sempre lanciata con un throw, che può essere scritto da te (per le tue eccezioni personalizzate) o avvenire nelle librerie Java, o in alcuni casi essere generata da una operazione non valida, per esempio la divisione per zero. L'eccezione una votla lanciata interrompe l'esecuzione del codice e risale i contesti fino a trovarsi in un blocco try/catch con un catch che soddisfi ("match") il tipo di eccezione.
Ora, se una operazione o una chiamata a un metodo può lanciare una eccezione, la chiamata a tale metodo deve essere racchiusa in un try/catch adatto, altrimenti il metodo da cui chiami deve contenere in firma la clausola throws, che dirà al metodo ancora più a monte che c'è una eccezione che potrà uscire da quel contesto.
E' un concetto molto semplice che si capisce ragionando per induzione su un caso base:
Codice:
void metodoA() throws MyExc{
throw new MyExc();
}
void metodoB(){
try{
metodoA();
}catch(MyExc e){
//fai qualcosa
}
//resto di B
}
void metodoC(){
metodoB();
//resto di C
}
In questo caso A lancia una eccezione, è il metodo B che chiama A e la chiamata è racchiusa in un blocco try/catch che fa match. L'eccezione viene catturata e non si propaga mai al metodo C, che quindi può ignorarla. SI noti che in questo caso il codice dal punto "//resto di B" viene eseguito.
Se invece non ci fosse il try/catch in metB:
Codice:
void metodoA() throws MyExc{
throw new MyExc();
}
void metodoB() [COLOR=#00ff00]throws MyExc[/COLOR]{
metodoA();
//resto di B
}
void metodoC(){
try{
metodoB();
}catch(MyExc){
//fai qualcosa
}
//resto di C
}
In questo caso, B non gestisce l'eccezione, e ne lascia l'onere ai contesti chiamanti (nel nostro caso metodoC). In metodoC c'è il try/catch giusto che gestisce l'eccezione MyExc lanciata da A e transitata per B.
In questo caso l'eccezione viene gestita a un livello superiore rispetto a B per cui il codice al punto "//resto di B"
non viene eseguito! Si noti che in questo caso, se B non gestisce l'eccezione, chi chiama B deve saperlo, per questo in firma a B si deve aggiungere il throws che dice "questo metodo può sollevare l'eccezione MyExc". Nota come a C non interessa se è stato B o A a lanciare l'eccezione, gli interessa solo sapere che chiamando metodoB, potrebbe arrivargli una eccezione generata in uno dei contesti inferiori. Infatti la clausola "throws" in firma ai metodi non distingue il metodo che contiene l'effettivo lancio dell'eccezione da quelli che si limitano a propagarla.
E se nemmeno C volesse gestire MyExc con un try/catch? Allora anche C in firma avrà "throws MyExc" e sarà chi chiama C che dovrà gestire la situazione.
ps: non l'ho evidenziato nel codice, ma se ci sono altre istruzioni dentro al blocco try successive all'istruzione che alza o propaga l'eccezione, queste
non vengono eseguite. Tipicamente si usa aggiungere il blocco finally dopo al catch per inserire quelle istruzioni che devono essere eseguite sia che ci sia una eccezione, sia che non ci sia. A un primo sguardo sembra che il blocco finally sia inutile, per esempio, dopo il catch nel metodo C, perchè sappiamo che "//resto di C" viene eseguito.
In realtà il finally ha un senso più astratto, tipicamente racchiude quelle istruzioni che noi vogliamo avere la certezza che vengano eseguite (per esempio chiudere stream o liberare altre risorse) perchè anche il "//resto di C" potrebbe sollevare eccezioni, e magari non controllate in C.
In Java7 mi pare abbiano aggiunto un costrutto simile al try (chiamato "try with resource") che permette di fare qualcosa di simile senza l'uso del finally.
ps2: parlavi di diversi tipi di eccezioni, una eccezione è un oggetto e quindi ha una sua classe che lo definisce. Le classi di eccezioni possono essere estese come le altre classi, per cui puoi avere diversi tipi di eccezione. Questo è importante da capire perchè quando inserisci il nome dell'eccezione in un catch, quel catch "matcherà" tutte le eccezioni di quel tipo e dei tipi sottotipo di quello indicato.
Se per esempio faccio un catch(Exception e), questo catch cattura tutte le eccezioni, perchè tutte sono sottoclasse di Exception.