Facciamo prima una premessa. Un metodo ha sempre e solo UNA "ragione" di uscita. O esce normalmente con un valore di ritorno oppure esce per una eccezione lanciandola fuori.
Due scenari abbastanza "eloquenti":
codice:public static int prova() { try { return 10; } finally { throw new RuntimeException("Eccezione!"); } }
Qui (forse) penseresti che il metodo esce normalmente restituendo 10. NO. Quando arriva al return 10, questa è la prima "ragione" di uscita e la mantiene per il momento. Poi però c'è il finally che deve essere onorato. Ma il finally lancia un RuntimeException. La prima ragione di uscita (il return di 10) viene buttata via e il metodo esce quindi per la eccezione, la seconda e ultima ragione di uscita.
Altro caso opposto:
codice:public static int prova() { try { throw new RuntimeException("Eccezione!"); } finally { return 10; } }
Quando si arriva al throw .... il lancio di RuntimeException è la prima "ragione" di uscita e viene tenuta per buona per il momento. Ma idem c'è un finally che deve essere eseguito. Ma il finally restituisce 10. Quindi la prima ragione di uscita viene buttata via e il metodo ritorna con l'ultima ragione di uscita, che è la restituzione di 10.
Nel tuo codice gli errori sono per altri motivi: le eccezioni che hai definito sono "checked" (controllate, estendono Exception).
Per il linguaggio Java è un ERRORE tentare di catturare una eccezione checked se il compilatore deduce che nel corpo del try quella eccezione checked NON viene lanciata mai. Perché il codice del catch sarebbe codice unreachable, non raggiungibile.
E se all'interno di un metodo viene lanciata una eccezione checked, allora DEVE essere dichiarata con il throws nella dichiarazione del metodo.