Clases de excepciones definidas en Java

El lenguaje Java pretende estandarizar el manejo de excepciones y por ello declara un amplio conjunto de clases de excepciones. Estas clases forman una jerarquía de clases, en la que la clase base es Throwable, que deriva directamente de la superclase base Object. 

De Throwable derivan dos clases: Error y Exception. Las excepciones del tipo Error son generadas por el sistema, se puede decir que se trata de errores irrecuperables; además es extraño que se produzcan, por ejemplo, «salir de la memoria de la máquina virtual»; por lo tanto, de producirse una excepción Error se propagará hasta salir por el método main(). Los nombres de las subclases que derivan de Error acaban con el sufijo Error, asi InternalError o NoClassDefFoundError.

De la clase Exception derivan clases de las que se instancian objetos (excepciones) para ser lanzados (throw), que pueden ser capturados por los correspondientes catch. Las clases derivadas de Exception se encuentran en los diversos paquetes de Java, todas tienen como nombre un identificador que indica su finalidad, terminado en Exception. La jerarquía de excepciones a partir de la base Exception:

RuntimeException

Hay excepciones que se propagan automáticamente por los métodos sin necesidad de especificarlas en la cabecera con throws. Todas las excepciones del tipo RuntimeException (división por cero, índice fuera de rango. . .) son de ese tipo. Sería muy tedioso especificar en todos los métodos que se puede propagar una excepción de, por ejemplo, división por cero, y por ello Java las propaga sin tener que especificar ese hecho. El método histog() de la clase ConHisto, tiene una expresión aritmética en la que puede producirse una excepción debido a una división por cero. La excepción se propaga y se capta en main().

       public class ConHisto {
                  static int histog(){
                             int k, r, z;
                             z=0; k=9;
                             while(k>1){
                                       r = (int)(Math.random()*13);
                                       System.out.print(«r = » + r);
                                       z = z+r+(2*r)/(r-k);
                                       System.out.println(» » +»z = » + z);
                                       k––;
                             }
                             return z;
                  }
                  static public void main(String[] args){
                             try{
                                         System.out.println(«Bloque try. Llamada a histog()»);
                                         histog();
                             }
                             catch(ArithmeticException a){
                                         System.out.println(«\tCaptura de excepción: » + a);
                             }
                             catch(RuntimeException r){
                                         System.out.println(«\tCaptura de excepción: » + r);
                             }
                  }
       }

Una ejecución (al depender de los números aleatorios no siempre se genera excepción) da lugar a esta salida:

       Bloque try. Llamada a histog()
       r = 8       z = -8
       r = 2       z = -6
       r = 11    z = 10
       r = 6       Captura de excepción: java.lang.ArithmeticException: / by zero

Al tomar r el valor 6 y k también 6 el programa detecta una división por cero y lanza la excepción. El método histog() no la captura, por lo que se propaga y alcanza main() que si la captura.

Excepciones comprobadas

Los métodos en los que se lanzan excepciones, bien directamente con throw, o bien porque llaman a otro método que propaga una excepción, deben comprobar, verificar, la excepción con el apropiado try-catch, si no la excepción se propaga. Esto exige que en la cabecera del método, con la cláusula throws se especifiquen los tipos de excepción que permite que sean propagados. Por ejemplo, el método readLine() de la clase BufferedReader puede lanzar la excepción del tipo IOException, en el caso de tratarla hay que especificar su propagación.

Ejemplo

      BufferedReader entrada = new BufferedReader(new InputStreamReader(System.in));
                           int entradaRango(){      ¡¡ error !!
                                  int d;
                                  do{
                                            d = Integer.parseInt(entrada.readLine());
                                  }while(d<=0||d>=10);
                                  return d;
                           }

El método entradaRango() tiene un error, error en tiempo de compilación. Se debe a que readLine() está implementado con esta cabecera:

      String readLine() throws IOException

Sin embargo entradaRango() no tiene un bloque try-catch para tratar la posible excepción, ni tampoco especifica que se puede propagar, lo cual es un error en Java. La forma de evitar el error es especificando la propagación de la excepción:

      int entradaRango() throws IOException

o bien capturarla. La propagación puede llegar al método main(), hay que capturarla. También está la opción de no capturarla y poner: main() throws IOException, aunque esto no es una práctica recomendada.

En el ejemplo aparece una llamada al método parseInt(), que puede generar la excepción NumberFormatException del tipo RuntimeException que no es necesario especificar su propagación. Las excepciones del tipo RuntimeException no hay que especificarlas.

Precaución

Es error de compilación no capturar las excepciones que propaga un método, o no especificar que se propagará una excepción. Java permite que en excepciones de la jerarquía que tiene como base RuntimeException no se especifique su propagación.

Métodos que informan de la excepción

La clase Exception tiene dos constructores, uno sin argumentos (constructor de manera predeterminada) y el otro con un argumento que se corresponde con una cadena de caracteres.

      Exception();
      Exception(String m);

Por lo que se puede lanzar la excepción con una cadena informativa:

      throw new Exception(«Error de lectura de archivo»);

El método getMessage() devuelve una cadena que contiene la descripción de la excepción, es la cadena pasada como argumento al constructor; su prototipo es:

      String getMessage();

A continuación se escribe un ejemplo en el que el método primero() llama a lotes(), que lanza una excepción; es capturada y se imprime el mensaje con el que fue construida.

      void lotes() throws Exception{
                 // . . .
                 throw new Exception(«Defecto en el producto.»);
      }
      void primero(){
                 try{
                             lotes();
                 }
                 catch(Exception mensaje){
                             System.out.println(mensaje.getMessage());
                 }
      }

El método printStackTrace() resulta muy útil para conocer la secuencia de llamadas a métodos realizadas hasta que llega al método donde se ha producido el problema, donde se ha lanzado la excepción. El propio nombre, printStackTrace(), ya indica su finalidad: escribir la cadena con que se inicializa el objeto excepción (si no se ha utilizado el constructor de manera predeterminada) y a continuación la identificación de la excepción junto al nombre de los métodos por donde se ha ido propagando.

Ejemplo

Se va a generar una excepción de división por cero. La excepción no es capturada por lo que se pierde en el método main() y termina la ejecución. En pantalla se muestra la traza de llamadas a los métodos que ha acabado con una excepción RuntimeException.

       public class ExcepcionAritmetica {
                static void atime(){
                           int k, r;
                           r = (int)Math.random();
                           System.out.println(«Método atime»);
                           k = 2/r;
                }
                static void batime(){
                           System.out.println(«Método batime»);
                           atime();
                }
                static void zatime(){
                           System.out.println(«Método zatime»);
                           batime();
                }
                static public void main(String[] args){
                           System.out.println(«Entrada al programa, main()»);
                           zatime();
                }
       }

La ejecución muestra por pantalla:

       Entrada al programa, main()
       Método zatime
       Método batime
       Método atime
       Exception in thread «main» java.lang.ArithmeticException:    / by zero
                           at excepcionaritmetica.ExcepcionAritmetica.atime(ExcepcionAritmetica.java:8)
                           at excepcionaritmetica.ExcepcionAritmetica.batime(ExcepcionAritmetica.java:12)
                           at excepcionaritmetica.ExcepcionAritmetica.zatime(ExcepcionAritmetica.java:16)
                           at excepcionaritmetica.ExcepcionAritmetica.main(ExcepcionAritmetica.java:20)

Nota

Una excepción que no es capturada por ninguno de los métodos donde se propaga, llegando al método main(), termina la ejecución y hace internamente una llamada a printStackTrace() que visualiza la ruta de llamadas a métodos y los números de línea.