Excepciones
Las excepciones son eventos inesperados que suceden durante la ejecución de un programa. Una excepción se puede deber a una condición de error, o tan sólo a un dato no previsto. En cualquier caso, en un lenguaje orientado a objetos, como lo es Java, se puede considerar que las excepciones mismas son objetos de clases que extienden, directa o indirectamente, Java.lang.exception.

Java.lang.Object

Java.lang.Throwable

Java.lang.Exception

BadLocationException

AWTException 

Otras más de 40 clases

IOException

FileNotFoundException

Otras más de 15 clases

Error

RuntimeException

Aprox. 25 subclases

Como se puede ver en esta jerarquía de clases, Java define las clases Exception y Error como subclases de Throwable, que denota cualquier objeto que se puede lanzar y atrapar. También, define la clase RuntimeException como subclase de Exception.

La clase Error se usa para condiciones anormales que se presenten en el ambiente de ejecución, como cuando se acaba la memoria. Los errores se pueden atrapar, pero es probable que no, porque en general señalan problemas que no se pueden manejar con elegancia. Un mensaje de error,  seguido de la terminación repentina del programa es lo máximo que podemos pretender. 

La clase Exception es la raíz de la jerarquía de excepciones. Se deben definir las excepciones especializadas (por ejemplo, la BoundaryViolationException) subclasificadas por Exception o RuntimeException. Nótese que las excepciones que no sean subclases de RuntimeException se deben declarar en la cláusula throws de cualquier método que las pueda lanzar.

Dijimos que las excepciones son objetos. Todos los tipos de excepción (es decir, cualquier clase diseñada para objetos lanzables) debe extender la clase Throwable o una de sus subclases. La clase Throwable contiene una cadena de texto que se puede utilizar para describir la excepción. Por convenio, los nuevos tipos de excepción extienden a Exception, una subclase de Throwable.

Las excepciones son principalmente excepciones comprobadas (también llamadas verificadas o síncronas), lo que significa que el compilador comprueba que nuestros métodos lanzan sólo las excepciones que ellos mismos han declarado que pueden lanzar. Las excepciones y errores estándar en tiempo de ejecución extienden una de las clases RuntimeException o Error, y se denominan excepciones no comprobadas (También llamadas asíncronas). Todas las excepciones que generemos deberían extender a Exception, siendo así excepciones comprobadas.

Las excepciones comprobadas representan condiciones que, aunque excepcionales, se puede esperar razonablemente que ocurran, y si ocurren deben ser consideradas de alguna forma. Al hacer que estas excepciones sean comprobadas se documenta la existencia de la excepción y se asegura que el que llama a un método tratará la excepción de alguna forma. Las excepciones no comprobadas representan condiciones que, hablando en términos generales, reflejan errores en la lógica de nuestro programa de los que no es posible recuperarse de forma razonable en ejecución. Por ejemplo, una excepción IndexOutOfBoundsException que se lanza al intentar acceder fuera de los límites de un array, nos indica que nuestro programa calcula los índices de forma incorrecta, o que falla al verificar un valor que se va a utilizar como índice. Estos errores deben corregirse en el código del programa. Como pueden producirse errores al escribir cualquier sentencia, sería totalmente imposible tener que declarar y capturar todas las excepciones que podrían surgir de estos errores, de ahí la existencia de las excepciones no comprobadas.
Algunas veces es útil tener más datos para descríbir la condición excepcional, además de la cadena de texto que proporciona Exception. En esos casos se puede extender a Exception creando una clase que contenga los datos añadidos (que generalmente se establecen en el constructor). Esto lo veremos mas adelante.

Lanzamiento de excepciones

En Java, las excepciones son objetos "lanzados" por un código que encuentra una condición inesperada. También pueden ser arrojadas por el ambiente de ejecución en Java, si llega a encontrar una condición inesperada, como por ejemplo si se acaba la memoria de objetos. Una excepción lanzada puede ser es atrapada por otro programa que la "maneja" de alguna manera, o bien el programa se termina en forma inesperada. 
Las excepciones se originan cuando un fragmento de programa Java encuentra alguna clase de problemas durante la ejecución y lanza un objeto de excepción, al que se identifica con un nombre descriptivo. 
Hagamos un primer ejemplo. Tomamos dos arreglos de elementos int de distinta longitud, y hacemos un ciclo donde nume[i] es dividido por deno[i]. El denominador tiene un 0 en el cuatro casillero, así que esperamos algún problema, sabido es que no se puede dividir por 0. También el hecho de ser los arreglos de distinta longitud, si comandamos el ciclo en función del más largo, debe dar problemas.

Excepciones, ej 01
Cociente 0 vale 5.0
Cociente 1 vale 5.0
Cociente 2 vale 5.0
Cociente 3 vale Infinity
Cociente 4 vale 5.0
java.lang.ArrayIndexOutOfBoundsException
	at Excep01.excep(Excep01.java:12)
	at Excep01.main(Excep01.java:18)
Exception in thread "main" 
Process Exit...

class Excep01{		//	     Su ejecución 
	static int nume[] = {10,20,30,40,50};
	static int deno[] =  {  2, 4,   6,  0,10,12}; 

	public static void excep() {
		int i;
		float coc;
		System.out.println("\nExcepciones, ej 01");
		for(i = 0; i < deno.length; i++){
			coc = (float)nume[i]/deno[i]; 
			System.out.println("Cociente "+i+" vale "+coc);
		}
	}
	public static void main(String args[]){
		excep();
	}
}

Hay una sorpresa. La división por 0 no causa una excepción, como esperábamos. El println nos dice: Cociente 3 vale Infinity. El tema de ir mas allá del fin del vector nume causa la excepción esperada,  java.lang.ArrayIndexOutOfBoundsException, (Excepción de índice fuera de los limites del vector). Como nosotros no capturamos ni tratamos esta excepción, ella llega a la Maquina Virtual Java que nos informa que ella ocurrió en la línea 12 del código fuente Java del método  excep() de la clase Excep01. Este método excep(), fué a su vez invocado en la línea 18 del método main(..) de la misma clase.  

Nos preocupa la sorpresa de la división por cero. Muchos libros de Java ejemplifican la división por cero como una excepción típica. Parece no ser tan así, o por lo menos aquí no ocurrió. También se encuentran ejemplos del método Carácter.toLowerCase((char) c), (Transforma mayúsculas a minúsculas). Se supone que si lo que tiene que transformar no es una letra habría problemas. Pues no los hay. Volviendo a nuestra división por cero, será que se puede seguir operando con una variable que vale  Infinity? Hagamos un programita que deje esto bien explicitado. 

import java.io.IOException;
class Infinity{
	public static void infinito() {
		double a = 10.0, b = 0.0, c, d,e = 5.0, f,g;
		System.out.println("\nPreocupandonos por el Infinito\n");
		c = a+e;

Preocupandonos por el Infinito
 valor de c = 15.0 
 valor de d = Infinity 
 valor de e = Infinity 
 valor de f = -Infinity
 valor de g = NaN
 Process Exit...

		System.out.println("valor de c = "+c);
		d = a/b;	// Supuestamente Infinity
		System.out.println("valor de d = "+d);
		e+=d;		// Supuestamente Infinity
		System.out.println("valor de e = "+e);
		f = a - d;	// Supuestamente -Infinity
		System.out.println("valor de f = "+f);
		g = d/e; 	// Que pasara ?
		System.out.println("valor de g = "+g);
	}
	public static void main(String args[]){
		infinito();
	}
}

Ahora lo sabemos. En Java se puede dividir por cero y el valor resultante es infinito. Este valor resultante es operable, por lo menos en algunas operaciones. Volveremos sobre esto. Ahora retornemos a nuestras excepciones: vamos a capturarla y tratarla nosotros.  

Atrapado de excepciones

Cuando se lanza una excepción ésta debe atraparse o de lo contrario el programa se terminará. En cualquier método particular puede atraparse la excepción en el generada, o bien se la puede “pasar” (Devolver, retroceder) hasta alguno de los métodos de la pila de llamadas, la última de las cuales activo nuestro método. Cuando se atrapa una excepción se puede analizar y manejar. La metodología general para manejar excepciones es "tratar" (try) de ejecutar algún fragmento de programa que pueda lanzar una excepción. Si se lanza una excepción, esa excepción queda atrapada y hace que el flujo de control salte a un bloque catch predefinido. Dentro del bloque catch se podrá entonces manejar la circunstancia excepcional.

Atrapando excepciones en el propio método 

Excepciones, ej 02
Cociente 0 vale 5.0
Cociente 1 vale 5.0
Cociente 2 vale 5.0
Cociente 3 vale Infinity
Cociente 4 vale 5.0
Excepcion capturada !!!
Exit excep(), class Excep02
Process Exit...

import java.io.IOException;
class Excep02{			//	      Ejecución 
	static int nume[] = {10,20,30,40,50};
	static int deno[] = { 2, 4, 6, 0,10,12}; 
	public static void excep() {
		int i;
		float coc;
		try{
		 	System.out.println("\nExcepciones, ej 02");
			for(i = 0; i < deno.length; i++){
				coc = (float)nume[i]/deno[i]; 
				System.out.println("Cociente "+i+" vale "+coc);
			}
			System.out.println("Saliendo del bloque de prueba ...");
		}catch(ArrayIndexOutOfBoundsException e){	
			System.out.println("Excepcion capturada !!!");
		}
		System.out.println("Exit excep(), class Excep02");
	}
	public static void main(String args[]){
		excep();
	}
}

Que pasó. Se produce la excepción tipo ArrayIndexOutOfBoundsException dentro del bloque de prueba (bloque try) y es capturada por la cláusula  catch(ArrayIndexOutOfBoundsException e). La captura es posible porque el catch captura eventos de excepción del tipo  ArrayIndexOut… El tratamiento que le damos es solamente imprimir el mensaje ("Excepcion del tipo ArrayIndexOut... capturada !!!") y nada mas. Después de esta captura termina la ejecución.

El ambiente Java comienza ejecutando el try{bloque_de_instrucciones_}. Si esa ejecución no genera excepciones, el flujo del control continúa con el primer paso de programa después del último renglón de todo el bloque try-catch, a menos que ese bloque incluya un bloque finally opcional. El bloque finally, de existir, se ejecuta independientemente de si las excepciones son lanzadas o atrapadas. Así, en este caso, si no se lanza alguna excepción, la ejecución avanza por el bloque try, salta al bloque finally y entonces continúa con el primer renglón que siga al último renglón del bloque try-catch.

Por otro lado, si try{bloque_de_instrucciones_} genera una excepción, la ejecución en el bloque try termina en ese punto, y salta al bloque catch cuyo tipo coincida con el de la excepción. Si la clase de la excepción generada es una subclase de la declarada en catch, la excepción será también capturada. Una vez que se completa la ejecución de ese bloque catch, el flujo pasa al bloque opcional finally, si existe, o bien de inmediato a la primera instrucción después del último renglón de todo el bloque try-catch, si no hay bloque finally. De otro modo, si no hay bloque catch que coincida con la excepción lanzada, el control pasa al bloque finally opcional si existe, y entonces la excepción se lanza de regreso al método llamador. 

Hemos visto un ejemplo de una excepción atrapada en el bloque match del propio método que genera la excepción. Ahora veremos el segundo caso, el método no tiene catch para el tipo de excepción producida. En realidad, en el ejemplo, no tiene ningún catch

 Atrapando excepciones “pasadas” desde métodos posteriores.

Vamos al mismo caso anterior, pero ejemplificando una excepción que no es capturado en el método donde se produce.  La excepción se produce en el último método, excep(), es capturada en el primero, Metodo01(), allí es tratada y, en nuestro ejemplo, termina el proceso. Gráficamente.

Main()

Metodo01() 
Captura de la excepción y su tratamiento

Metodo02()

excep()
Se produce la excepción

Línea continua: Flujo de llamadas.
Línea de trazo: Retorno desde la excepción

Entrada a Metodo01()
Entrada a Metodo02()
Entrada a excep()
Cociente 0 vale 5.0
Cociente 1 vale 5.0
Cociente 2 vale 5.0
Cociente 3 vale Infinity
Cociente 4 vale 5.0
Capturada en Metodo01()!!!
Salida de Metodo01()
Process Exit...

import java.io.IOException;			//  Ejecución 
class Excep03{
	static int nume[] = {10,20,30,40,50};
	static int deno[] = { 2, 4, 6, 0,10,12}; 
	public static void Metodo01(){
		System.out.println("\nEntrada a Metodo01()");
		try{	//  en el bloque de prueba esta la llamada a
			Metodo02();
		}catch(ArrayIndexOutOfBoundsException e){	
			System.out.println("Capturada en Metodo01()!!!");
		}
		System.out.println("Salida de Metodo01()");
	}	
	private static void Metodo02(){
		System.out.println("Entrada a Metodo02()");
		excep();
		System.out.println("Salida de Metodo02()");
	}	
	private static void excep() throws ArrayIndexOutOfBoundsException{
		int i;
		float coc;
		System.out.println("Entrada a excep()");
		for(i = 0; i < deno.length; i++){
			coc = (float)nume[i]/deno[i]; 
			System.out.println("Cociente "+i+" vale "+coc);
		}
		System.out.println("Salida de excep(), Excep03");
	}
	public static void main(String args[]){
		Metodo01();
	}
}
La excepción se produce, como antes, en el método excep(), pero  ahora no se la captura, solo declaramos que se dispara, mediante la cláusula throws. El tratamiento de excepciones se realiza en metodo01(). Allí tenemos el bloque try y la cláusula de captura. En el seguimiento de la ejecución vemos que efectivamente hay un retroceso hasta el método llamador que contiene la captura.
 Si consideramos que en general cada método contiene decisiones que definen sobre cual método se invoca a seguir, esto significa que en cada uno de ellos podemos continuar por distintos caminos. Pero en el punto de partida, o en un punto intermedio, o selectivamente, podremos tratar las excepciones generadas mas adelante. La captura es por tipo de objeto de excepción, esto significa que podemos capturar selectivamente.  Esta captura será tanto de excepciones ya previstas en el lenguaje Java como propias nuestras, puesto que podemos definir nuestras propias excepciones, extendiendo clases existentes. Y ya que estamos, generemos una excepción para la división por cero.
 
Generando nuestras propias excepciones. 
Hemos dicho que la captura es por tipo de excepción y que si el tipo tiene subclases, serán atrapadas allí. O sea que si declaramos una cláusula match con tipo Excepción, allí quedarían capturadas la mayoriía de las excepciones, cosa que no deseamos. Necesitamos ser más específicos. 

Una excepción de división por cero es del tipo aritmético. Si entramos en el help de Java, por contenido y tipeamos Arithmetic <enter>, obteneos varios tópicos: la clase y dos constructores. 

Seleccionamos el primero y <display > 

La parte superior del capture muestra el esquema hereditario al cual la clase ArithmeticException está subordinada. Pertenece al paquete java.lang y extiende RuntimeException. Si traducimos la última línea: Disparada cuando ha ocurrido una condición aritmética excepcional. Por ejemplo, una división “entero por cero” dispara una instancia de esta clase.

Ahora las cosas comienzan a aclararse. Solo la división por cero  en números enteros produce una excepción. Nosotros dividimos por cero números reales, allí no ocurre esta excepción. Si queremos que esto ocurra, podemos codificar una clase que extienda ArithmeticException  y hacerlo allí.

import java.io.IOException;
class RealDivideExcep extends ArithmeticException{
	static int nume[] = {10,20,30,40,50};
	static int deno[] = { 2, 4, 6, 0,10,12}; 

Excepciones, ej 04
Cociente 0 vale 5.0
Cociente 1 vale 5.0
Cociente 2 vale 5.0
Excepcion, RealDivideExcep... capturada !!!
Exit RealDivideExcep()
Process Exit...

	
	public RealDivideExcep(){
		super();
	}
	public void excep() { //  Ejecución 
		int i;
		float coc;
		try{
		 	System.out.println("\nExcepciones, ej 04");
			for(i = 0; i < deno.length; i++){
				if (deno[i] == 0)		//  La situación que nos preocupa
					throw new RealDivideExcep();	//  Disparamos nuestra excepción 
				coc = (float)nume[i]/deno[i]; 
				System.out.println("Cociente "+i+" vale "+coc);
			}
			System.out.println("Saliendo del bloque de prueba ...");
		}catch(ArrayIndexOutOfBoundsException e){	
			System.out.println("Excepcion, ArrayIndexOut... capturada !!!");
		}catch(ArithmeticException e){	
			System.out.println("Excepcion, RealDivideExcep... capturada !!!");
		}
		System.out.println("Exit RealDivideExcep()");
	}

	public static void main(String args[]){
		RealDivideExcep rDivEx = new RealDivideExcep();
		rDivEx.excep();
	}
}

Observamos que:
Declarando el catch con la clase ArithmeticException capturamos la RealDivideExcep.
La excepción ArrayIndexOutOfBoundsException no llega a producirse, salimos antes.