Control de excepciones

GESTIÓN DE EXCEPCIONES Y ERRORES
A. Introducción

El control de flujo en un programa Java puede hacerse mediante las ya conocidas sentencias estructuradas (if, while, return). Pero Java va mucho más allá, mediante una técnica de programación denominada gestión de excepciones.
Mediante las excepciones se podrá evitar repetir continuamente código, en busca de un posible error, y avisar a otros objetos de una condición anormal de ejecución durante un programa.
Durante este capítulo estudiaremos la gestión de excepciones y errores, sin pretender profundizar demasiado, pero sí fijando la base conceptual de lo que este modo de programación supone.
Mediante la gestión de excepciones se prescindirá de sentencias de control de errores del tipo:
if ( error == true )

  return ERROR;


B. Tipos de excepciones
Existen varios tipos fundamentales de excepciones:
  • Error: Excepciones que indican problemas muy graves, que suelen ser no recuperables y no deben casi nunca ser capturadas.

  • Exception: Excepciones no definitivas, pero que se detectan fuera del tiempo de ejecución.

  • RuntimeException: Excepciones que se dan durante la ejecución del programa.

Todas las excepciones tienen como clase base la clase Throwable, que está incluida en el paquete java.lang, y sus métodos son:
  • Trowable( String mensaje ); Constructor. La cadena es opcional

  • Throwable fillInStackTrace(); Llena la pila de traza de ejecución.

  • String getLocalizedMessage(); Crea una descripción local de este objeto.

  • String getMessage(); Devuelve la cadena de error del objeto.

  • void printStackTrace( PrintStream_o_PrintWriter s ); Imprime este objeto y su traza en el flujo del parámetro s, o en la salida estándar (por defecto).

  • String toString; Devuelve una breve descripción del objeto.
C. Funcionamientoa.) Introducción
Para que el sistema de gestión de excepciones funcione, se ha de trabajar en dos partes de los programas:
  • Definir qué partes de los programas crean una excepción y bajo qué condiciones. Para ello se utilizan las palabras reservadas throw y throws.

  • Comprobar en ciertas partes de los programas si una excepción se ha producido, y actuar en consecuencia. Para ello se utilizan las palabras reservadas try, catch y finally.
b.) Manejo de excepciones: try - catch - finally
Cuando el programador va a ejecutar un trozo de código que pueda provocar una excepción (por ejemplo, una lectura en un fichero), debe incluir este fragmento de código dentro de un bloque try:
try {

  // Código posiblemente problemático

}


Pero lo importante es cómo controlar qué hacer con la posible excepción que se cree. Para ello se utilizan las clausulas catch, en las que se especifica que acción realizar:
try {

  // Código posiblemente problemático

} catch( tipo_de_excepcion e) {

  // Código para solucionar la excepción e

} catch( tipo_de_excepcion_mas_general e) {

  // Código para solucionar la excepción e

}


En el ejemplo se observa que se pueden anidar sentencias catch, pero conviene hacerlo indicando en último lugar las excepciones más generales (es decir, que se encuentren más arriba en el árbol de herencia de excepciones), porque el intérprete Java ejecutará aquel bloque de código catch cuyo parámetro sea del tipo de una excepción lanzada.
Si por ejemplo se intentase capturar primero una excepción Throwable, nunca llegaríamos a gestionar una excepción Runtime, puesto que cualquier clase hija de Runtime es también hija de Throwable, por herencia.
Si no se ha lanzado ninguna excepción el código continúa sin ejecutar ninguna sentencia catch.
Pero, ¿y si quiero realizar una acción común a todas las opciones?. Para insertar fragmentos de código que se ejecuten tras la gestión de las excepciones. Este código se ejecutará tanto si se ha tratado una excepción (catch) como sino. Este tipo de código se inserta en una sentencia finally, que será ejecutada tras el bloque try o catch:

try {

} catch( Exception e ) {

} finally {

  // Se ejecutara tras try o catch

}


c.) Lanzamiento de excepciones: throw - throws
Muchas veces el programador dentro de un determinado método deberá comprobar si alguna condición de excepción se cumple, y si es así lanzarla. Para ello se utilizan las palabras reservadas throw y throws.
Por una parte la excepción se lanza mediante la sentencia throw:
if ( condicion_de_excepcion == true )

  throw new miExcepcion();


Se puede observar que hemos creado un objeto de la clase miExcepcion, puesto que las excepciones son objetos y por tanto deberán ser instanciadas antes de ser lanzadas.
Aquellos métodos que pueden lanzar excepciones, deben cuáles son esas excepciones en su declaración. Para ello se utiliza la sentencia throws:
tipo_devuelto miMetodoLanzador() throws miExcep1, miExcep2 {

  // Codigo capaz de lanzar excepciones miExcep1 y miExcep2

}


Se puede observar que cuando se pueden lanzar en el método más de una excepción se deben indicar en su declaración separadas por comas.
d.) Ejemplo de gestión de excepciones
Ahora que ya sabemos cómo funciona este sistema, conviene ver al menos un pequeño ejemplo, que ilustre al lector en el uso de las excepciones:
// Creo una excepción personalizada

class MiExcepcion extends Exception {

  MiExcepcion(){

    super(); // constructor por defecto de Exception

  }

  MiExcepcion( String cadena ){

    super( cadena ); // constructor param. de Exception

  }

}


// Esta clase lanzará la excepción

class Lanzadora {

  void lanzaSiNegativo( int param ) throws MiExcepcion {

    if ( param < 0 )

      throw new MiExcepcion( "Numero negativo" );

  }

}


class Excepciones {

  public static void main( String[] args ) {

    // Para leer un fichero

    Lanzadora lanza = new Lanzadora();

    FileInputStream entrada = null;

    int leo;

    try {

      entrada = new FileInputStream( "fich.txt" );

      while ( ( leo = entrada.read() ) != -1 )

        lanza.lanzaSiNegativo( leo );

      entrada.close();

      System.out.println( "Todo fue bien" );

    } catch ( MiExcepcion e ){ // Personalizada

      System.out.println( "Excepcion: " + e.getMessage() );

    } catch ( IOException e ){ // Estándar

      System.out.println( "Excepcion: " + e.getMessage() );

    } finally {

      if ( entrada != null )

        try {

          entrada.close(); // Siempre queda cerrado

        } catch ( Exception e ) {

        System.out.println( "Excepcion: " + e.getMessage() );

        }

      System.out.println( "Fichero cerrado." );

    }

  }

}


class Excepciones {

  public static void main( String[] args ) {

    // Para leer un fichero

    FileInputStream entrada = null;

    Lanzadora lanza = new Lanzadora();

    int leo;

    try {

      entrada = new FileInputStream("fich.txt");

      while ( ( leo = entrada.read() ) != -1 )

        lanza.lanzaSiNegativo( leo );

      System.out.println( "Todo fue bien" );

    } catch ( MiExcepcion e ){ // Personalizada

      System.out.println( "Excepcion: " + e.getMessage() );

    } catch ( IOException e ){ // Estándar

      System.out.println( "Excepcion: " + e.getMessage() );

    } finally {

      entrada.close(); // Así el fichero siempre queda cerrado

      System.out.println( "Fichero cerrado" );

    }

  }

}


Este programa lee un fichero (fichero.txt), y lee su contenido en forma de números.
Si alguno de los números leídos es negativo, lanza una excepción MiExcepcion, Además gestiona la excepción IOException, que es una excepción de las que Java incluye y que se lanza si hay algún problema en una operación de entrada/salida.
Ambas excepciones son gestionadas, imprimiendo su contenido (cadena de error) por pantalla.
La salida de este programa, suponiendo un número negativo sería:
Excepcion: Numero negativo
Fichero cerrado

En el caso de que no hubiera ningún número negativo sería:
Todo fue bien
Fichero cerrado

En el caso de que se produjese un error de E/S, al leer el primer número, sería:
Excepcion: java.io.IOException
Fichero cerrado