Las excepciones son situaciones anómalas que se producen en la ejecución de los programas, como puede
ser una división por cero, o un fallo en la
apertura de un fichero.
Mediante el mecanismo de excepciones se intentan poder recuperar la anomalía y que el programa pueda seguir
funcionando.
Los errores son situaciones irrecuperables que se escapan al control del
programador, como por ejemplo un fallo en la máquina virtual.
En Java tanto las excepciones y los errores son clases que derivan de Throwable.
Desde el punto de vista del tratamiento de las excepciones se puede
distinguir tres categorías:
o
Excepciones comprobadas, marcadas (checked exceptions).
Su captura es obligatoria. Derivan directamente de
Exception, pero no de Error ni de RuntimeException. Se producen al invocar determinados métodos de ciertas clases, por circunstancias internas al método. Si se invoca a algún método que pueda producir una excepción de este tipo es necesario capturarla para evitar un error de compilación. Por ejemplo, si se pasa al constructor de FileReader el nombre de un archivo, ese constructor puede
lanzar una FileNotFoundException. Esa excepción debe ser notificada al usuario, al que
posiblemente habrá que pedir un nuevo nombre de archivo.
o
Excepciones de ejecución, no comprobadas, o no
marcadas (unchecked exceptions). Son aquellas que derivan de RuntimeException. Se caracterizan porque normalmente la aplicación no se puede recuperar frente a ellas. Por ejemplo, si se pasa un null al constructor del FileReader, éste lanzara una NullPointerException. La aplicación puede capturar esta excepción, pero tiene más sentido corregir el error lógico que hay en la aplicación, y que ha dado lugar esta excepción.
o
Errores. Son aquellas que derivan de Error directamente. Se deben a circunstancias ajenas al código del programa. Si el FileReader anterior consigue abrir el archivo y se produce un fallo en el disco, se
lanzará un IOError. El programa debe notificar al usuario la aparición de este error, pero posiblemente no puede hacer otra cosa que mostrar un
volcado de pila y concluir la ejecución del programa.
Las excepciones comprobadas o marcadas (checked exceptions), tienen que cumplir necesariamente el requisito de Captura o Especificación, es decir, todo bloque de código que lance una excepción de este tipo tiene que capturar esa excepción, o notificar que puede lanzar excepciones de ese tipo.
Las excepciones de ejecución o no comprobadas y los errores no están obligadas a cumplir el requisito de captura o especificación.
Para capturar una excepción, es preciso emplear un bloque catch():
catch(TipoDeExcepcionCapturado
| OtroTipoDeExcepcion ex)
{
// Código que
procesa esta excepción
}
Para notificar o especificar que un código pueden lanzar una excepción, es preciso emplear una sentencia throws:
throws Excepcion_1,
Excepcion_2,... Excepcion_N;
Cuando se produce la excepción en un programa, se crea un objeto de la clase de
excepción que se ha dado y se lanza a la línea de código donde ha tenido lugar.
El mecanismo de control de excepciones permite capturar el objeto lanzado y
realizar una serie de acciones programadas para el tipo de excepción.
La forma de llevarlo a cabo es mediante el uso de las instrucciones try, catch y finally.
Estructura genérica para el tratamiento de excepciones:
try {
// Código vigilado
}
catch(ExcepcionCapturada_1
ex1) {
// Código que
procesa ex1
}
catch(ExcepcionCapturada_2
ex2) {
// Código que
procesa ex2
}
finally {
// Código que se
ejecuta siempre,
// tanto si se
procesa una excepción como si no
}
Try
o
Delimita el conjunto de líneas del programa donde se puede producir la
excepción. Cuando
se produce, el control pasa al bloque de código Catch correspondiente a la excepción producida, pasándole el objeto asociado a la excepción producida.
Catch
o
Conjunto de instrucciones que tratan o procesan un determinado tipo de
excepción.
o
Pueden aparecer varios bloques de este tipo seguidos. Cada uno se encargará de un tipo de excepción, o incluso pueden aparecer distintos bloques
para clases de excepciones en distintos niveles de la jerarquía de clases. Por ejemplo un tratamiento específico para una subclase y otro más genérico para la clase.
o
Sólo se puede ejecutar un bloque catch, pasando el
control al bloque Finally o al punto siguiente después del último catch, si no existiera Finally. Si para una
excepción producida se pueden aplicar varios catch, se
procederá de forma secuencial y se ejecutará el primero que esté en el código. Por ejemplo si se produce una excepción NullPointerException y tenemos un catch para tratar NullPointerException
y otro catch más genérico para tratar RuntimeException, se
ejecutará el que esté primero, por eso es más adecuado colocar los más específicos primero.
Finally
o
Instrucciones opcionales que se ejecutan siempre que aparezcan,
independientemente de la opción catch que se haya ejecutado. Incluso se ejecutan
aunque no se haya producido ninguna excepción en el bloque try.
/* Los
desbordamientos de tipos no producen ni avisos ni excepciones por lo que deben
ser vigilados por el programador */
package
pruebaexcepciones;
public class
PruebaExcepciones {
public static void
main(String[] args) {
int n1, n2 = 0;
n1 = Integer.MAX_VALUE;
try {
n2 = n1 + 1; //desbordamiento de tipo, no tratado
System.out.printf("n1 vale:
%d, y n2 vale: %d%n", n1, n2);
} catch (Exception e) { //este código no se
va a ejecutar
System.out.println("Exception
---> " + e);
}
System.out.printf("%nComentario
entre un try y otro%n%n");
try {
n2 = n1 / 0;
} catch (RuntimeException re) {
System.out.println("Excepción
---> " + re);
} finally {
System.out.println("n2 vale:
" + n2);
}
System.out.printf("Finalmente
n1 vale: %d, y n2 vale: %d%n", n1, n2);
}
}
El resultado de la ejecución es:
n1
vale: 2147483647, y n2 vale: -2147483648
--Comentario
entre un try y otro--
Excepción ---> java.lang.ArithmeticException: / by zero
En
finally, n2 vale: -2147483648
Última línea, n1 vale: 2147483647, y n2 vale: -2147483648
Si en lugar de dividir por cero,
dividimos por 1, no se produce la excepción, pero la cláusula finally se ejecuta igualmente, aun no
produciéndose excepción, y se obtiene:
n1
vale: 2147483647, y n2 vale: -2147483648
--Comentario
entre un try y otro--
En
finally, n2 vale: 2147483647
Última línea, n1 vale: 2147483647, y n2 vale: 2147483647
Ejemplo de
excepción comprobada, o marcada (checked exception), tratada mediante try … catch
para resolver una IOException:
public void
escribirLista_2(File f) {
PrintWriter salida = null;
try {
FileWriter fw = new FileWriter(f);
salida = new PrintWriter(fw);
salida.println(“Archivo " +
f.getName());
for (int i = 0; i <
NUM_ELEMENTOS; ++i) {
salida.println("El valor situado en la posición
"
+ i + " es " + lista.get(i));
}
} catch (IOException ex) {
System.out.println("\nError al
escribir el archivo " + f.getName());
System.out.println(ex);
if (null != salida) salida.close();
}
}
Ejemplo de
excepción comprobada, o marcada (checked exception), notificada mediante throws
(requisito de especificación) para resolver una IOException:
public void
escribirLista_1(File f) throws IOException {
FileWriter fw = new FileWriter(f);
PrintWriter salida = new
PrintWriter(fw);
salida.println(“Archivo “ +
f.getName());
for (int i = 0; i < NUM_ELEMENTOS; ++i)
{
salida.println(“Valor “ + i + “ = “
+ lista.get(i));
}
salida.close();
}
En los dos ejemplos
anteriores se pueden producir excepciones de ejecución, del tipo IndexOutOfBoundsException, por ejemplo si
sobrepasáramos los límites de la lista.
Estas excepciones son no comprobadas o no marcadas (unchecked exception), por lo que el
compilador no exige tratarlas, pero para depurar el código, podríamos hacerlo:
… try {
FileWriter fw = new FileWriter(f);
salida = new PrintWriter(fw);
salida.println("Este es el
archivo " + f.getName());
for (int i = 0; i < NUM_ELEMENTOS; ++i) {
salida.println("Valor
" + i + " = " + lista.get(i));
}
} catch (IOException ex) {
System.out.println("\nError al
escribir el archivo " + f.getName());
System.out.println(ex);
if (null != salida) {
salida.close();
}
} catch (IndexOutOfBoundsException ie)
{
System.out.println("Error en los
límites de la lista\n");
System.out.println(ie);
} finally {
…
Propagación de excepciones
Cuando se produce una excepción y no es tratada, se propaga por la pila de
llamadas, pasando a un nivel superior, hasta que encuentre un nivel que la
trate o hasta que llegue al programa principal. En el caso de que en el
programa principal tampoco se haga, pasará el control a la máquina virtual, que se encargará de hacer un volcado de memoria y de finalizar el
programa.
En el caso de las excepciones marcadas, puede tratarse, o bien sólo notificarla incluyendo throws en la cabecera del método, para pasar el control a un nivel superior de la pila donde deberá ser tratada.
Lanzamiento de excepciones
En ocasiones puede ser necesario, o útil, lanzar una excepción para que sea tratada en otro punto de la aplicación.
La forma de hacerlo es mediante:
thow excepcion;
siendo excepcion un objeto de la clase Exception o de alguna de sus clases derivadas. Puede
lanzarse desde cualquier punto del programa incluidas las cláusulas catch.
Si se trata de una excepción marcada, deberá notificarse en la cabecera del método desde donde se lance.
Ejemplo 1, se lanza una excepción sin tratar y se captura en el nivel superior:
public class
ClaseSuperior {
public static void main(String[] args) {
ClaseInferior ci = new ClaseInferior();
try {
ci.lanzar();
// ci.lanzar_capturar_y_relanzar();
} catch (Exception e) {
System.out.println("\nExcepción capturada en main ClaseSuperior
---> " + e);
}
}
}
public class
ClaseInferior{
void lanzar() throws Exception {
System.out.println(“Método lanzar()\n");
throw new Exception("Lanzada
desde método lanzar\n");
}…}
Ejecución:
Método lanzar()
Excepción capturada en main ClaseSuperior ---> java.lang.Exception: Lanzada
desde método lanzar
Ejemplo 2, se lanza una excepción, se trata y se relanza al nivel superior:
public class ClaseInferior{
void lanzar_capturar_y_relanzar() throws
Exception {
// Se lanza una excepción y se captura
de inmediato y se relanza
try {
System.out.println("Prueba
lanzar, capturar…()\n");
throw new Exception("Lanzada desde try");
} catch (Exception ex) {
System.out.println("Excepción capturada en lanzar,
capturar…()");
System.out.println("\nSe relanza
la excepción");
throw ex;
} finally {
System.out.println(“Estamos en
finally");
}
}
}
Ejecución:
Prueba lanzar, capturar…()
Excepción capturada en lanzar, capturar…()
Se relanza la excepción
Estamos en finally
Excepción capturada en main ClaseSuperior ---> java.lang.Exception: Lanzada
desde try
Métodos más importantes de la clase Exception:
o String getMessage()
Devuelve el mensaje asociado a la excepción sobre la que se aplica.
o void printStackTrace()
Imprime en la consola el volcado de pantalla
asociado a la excepción.
o void printStackTrace(PrintStream ps)
o void printStackTrace(PrintWriter pw)
Variantes de printStackTrace a los que
se les puede pasar un PrintStream o un PrintWriter y que nos
permitirá, por ejemplo, almacenar el
volcado en un fichero.
Ejemplo 1 modificado:
package
gestexcep;
import
java.io.FileNotFoundException;
import
java.io.PrintWriter;
public class
ClaseSuperior {
public static
void main(String[] args) throws FileNotFoundException {
// TODO code application logic here
ClaseInferior ci = new ClaseInferior();
PrintWriter pw = new
PrintWriter("fichero.log");
try {
//ci.lanzar();
ci.lanzar_capturar_y_relanzar();
} catch (Exception e) {
System.out.println("\nExcepción capturada en main ClaseSuperior
---> " + e);
System.out.println(e.getMessage());
e.printStackTrace(); //útil en modo depuración
e.printStackTrace(pw);
pw.close();
}
}
}
Ejecución:
Prueba lanzar, capturar…()
Excepción capturada en lanzar, capturar…()
Se relanza la excepción
Estamos en finally
Excepción capturada en main ClaseSuperior ---> java.lang.Exception: Lanzada
desde try
Lanzada desde try
java.lang.Exception: Lanzada desde try
at
gestexcep.ClaseInferior.lanzar_capturar_y_relanzar(ClaseInferior.java:24)
at
gestexcep.ClaseSuperior.main(ClaseSuperior.java:28)