Excepeciones en Java

¿Qué son las excepciones?

A veces, cuando llamamos a un método, puede que algo vaya mal dentro del método y no pueda devolvernos un resultado. Por ejemplo, puede que le pasemos parámetros al método que no le gustan, como algún null, algún valor fuera de rango, etc. Es posible que el método quiera acceder a un fichero o a una base de datos y tenga problemas porque no existe el fichero, no tiene permisos de lectura, no puede conectarse con la base de datos, etc.
Tradicionalmente estos métodos o funciones, en lenguajes como C, devolvían un valor que se reconocía como no válido. Podía ser un NULL o más habitualmente, un -1. Por ejemplo, la función open() de C intenta abrir un fichero. Si lo consigue, devuelve un entero que es el descriptor del fichero. Si no lo consigue, devuelve -1.
Esta opción no es la más elegante. Muchas veces no podemos devolver un valor para indicar error. Por ejemplo, a la función atoi() de C se le pasa una cadena de texto como "123" y devuelve el entero 123. Esta función no puede devolver -1 para indicar error, puesto que si le pasamos la cadena "-1", debería devolver justamente -1.
Por ello, los métodos en los lenguajes más modernos como C++ o Java, además de devolver un valor, pueden "lanzar una excepción". Por ejemplo, la clase Writer de java, en su método write(), se le pasan unos bytes para escribir en algún sitio, un fichero por ejemplo. Si hay algún error, "lanza una excepción" IOException, para indicar que ha habido algún error.

Capturar excepciones: try-catch

Para llamar a ese método, debemos "capturar" la excepción, poniendo el código en bloques try-catch, de esta manera
Writer w = ...;
byte [] unosBytes = ...;
try
{
   unaLineaDeCodigo();
   w.write (unosBytes);
   otraLineaDeCodigo();
}
catch (IOExcepcion e)
{
   System.err.println("Error al escribir: "+e.toString());
}
Cuando se ejecuta este código, primero se ejecuta unaLineaDeCodigo(), luego w.write(unosBytes) y si no hay problemas, se ejecuta otraLineaDeCodigo(). En caso de problemas con w.write(unosBytes), la ejecución salta automáticamente al bloque catch(), ejecutándose el System.err.println(). NO se ejecutaría otraLineaDeCodigo().
Si el código dentro del bloque try genera varios tipos de excepciones, podemos poner varios bloques catch, para capturar por separado cada tipo. Por ejemplo,
try
{
   codigo();
   masCodigo();
   yMas();
}
catch (IOException e)
{
   trataIOException(e);
}
catch (DataFormatException e2)
{
   trataDataFormatException(e2);
}
catch (...)

Herencia de Excepciones

Las excepciones definidas en java heredan todas de Exception. Si miras en la API, verás que hay toda una jerarquía de excepciones hijas unas de otras. Esta jerarquía es importante, puesto que los bloques catch capturan un tipo de excepción Y TODAS sus hijas. Por ejemplo, el siguiente bloque catch
try
{
   ...
}
catch (Exception e)
{
   trataTodasLasExcepciones(e);
}
captura las Exception y todas las excepciones hijas de Exception, es decir, todas.
Los catch se tratan en el orden en que se han escrito. Si se produce una excepción, primero se mira si cuadra en el primer catch. Si no cuadra, se pasa al siguiente, y así sucesivamente. Si en el ejemplo anterior quisieramos, por ejemplo, tratar las IOException de una forma especial y las demás de la misma forma, podemos poner
try
{
   ...
}
catch (IOException e)
{
   trataIOException(e);
}
catch (Exception e2)
{
   trataTodasLasExcepciones(e2);
}
y es importante poner el catch de IOException delante de el de Exception. Si lo hacemos al revés
try
{
   ...
}
catch (Exception e2)
{
   trataTodasLasExcepciones(e2);
}
catch (IOException e)
{
   trataIOException(e);
}
primero se mira si la excepción es o es hija de Exception. IOException, como todas las excepciones, es hija de Exception, así que entraría por el catch de Exception. El catch de IOException se ignoraría siempre.

finally

Muchas veces es importante que se ejecute algo tanto si se produce una excepción como si no. Por ejemplo, si abrimos un fichero para leerlo, es importante asegurarse que luego lo cerramos. Si nos conectamos a una base de datos para hacer una consulta, suele ser importante cerrar después la conexión. Esto debe hacerse independientemente de que las operaciones de lectura tengan o no éxito.
Para ello, try-catch admite un tercer bloque finally. Este bloque finally se ejecutará siempre, pase lo que pase, cuando se termine el try o el catch.
try
{
   abreFichero();
   leeFichero();
   return;
}
catch (Exception e)
{
   trataError(e);
}
finally
{
   cierraFichero();
}
Si al abrir el fichero o al leerlo salta una excepción, se tratará la excepción y luego se ejecutará el finally, cerrando el fichero. Si no hay excepción, después del try, se ejecutará también el finally y se cerrará el fichero. He puesto un return a posta. El finally se ejecutará incluso aunque en el código haya un return.

Lanzar nuestras propias excepciones

Si hacemos métodos propios, podemos lanzar excepciones en caso de que algo vaya mal. Estas excepciones pueden ser las de java o bien unas que nos creemos nosotros.
Para crear nuestra excepción, base con hacer una clase que herede de Exception.
public class MiExcepcion extends Exception
{
   public MiExcepcion(...) {
      ...
   }
   public void miPropioMetodo(...) {
      ...
   }
}
Nuestro método que lanza esta excepción, o una de java, debe declararse con un throws
public class MiClase
{
   public int miMetodoQueLanzaExcepcionSiHayFallo (...) throws MiExcepcion
   {
      ejecutoMiCodigo();
      // si hay fallo, se lanza la excepcion.
      if (hayFallo())
         throw new MiExcepcion(...);
      return resultado;
   }
}
Para lanzar la excepción, como vemos en ese código, basta con hacer un new de MiExcepción y lanzarla con throw.

¿Cuándo lanzar excepciones?

Cuando estamos codificando un método puede que nos fallen cosas y no podamos seguir haciendo lo que pretendemos. ¿Debemos lanzar siempre una excepción es estos casos? ¿o debemos mostrar el error por pantalla? ¿quizás debemos tratar de solventar el error y no hacer nada?.
Desde luego, depende un poco de los gustos de cada cual. Sin embargo, suele ser buena idea hacer lo siguiente:
  • Si el método es un método que pensamos reutilizar mucho, en muchos proyectos distintos, es mejor lanzar una excepción. Dejamos la decisión de qué hacer en caso de error al que utilice nuestro método. Nosotros símplemente le informamos que ha ocurrido un error para que él lo trate.
  • Si el método es de la parte de alto nivel de nuestra aplicación, debemos tratar directamente el error y no lanzar la excepción. En este nivel alto debemos saber qué hace nuestra aplicación con los errores: mostrarlos en una ventana de pop-up, escribirlos como salida de texto, ignorarlos, etc.

No hay comentarios:

Publicar un comentario

Gracias por comentar en mi blog. Saludos.