Cuando se ejecuta la instrucción break, la instrucción siguiente será la que sigue al ciclo que contenía ese break:
Cuando se llega a ejecutar el break, la ejecución continúa con
la instrucción B y no con A. De la misma forma, cuando se ejecuta un return,
la ejecución prosigue en el método que llamó al actual.
...
while ( ... ) {
...
if ( ... )
break;
... instrucción A ...
}
... instrucción B ...
La excepciones pueden ser vistas como una forma más avanzada de break, en donde se puede incluso retomar la ejecución en otro método. Por ejemplo, el mismo ejemplo anterior se puede reprogramar de la siguiente manera:
MiExcp debe ser una subclase de Exception. La mayoría de las
veces no contiene ni variables de instancia ni métodos útiles. En el
ejemplo se puede definir simplemente como:
...
try {
while ( ... ) {
...
if ( ... )
throw new MiExcp();
... instrucción A ...
}
}
catch(MiExcp e) {
}
... instrucción B ...
Por supuesto, cuando se desea terminar un ciclo, la instrucción break
es la más adecuada. Las excepciones se usan cuando se desea alterar
el flujo normal de las instrucciones y por lo tanto, como su nombre
lo indica, se emplean para casos excepcionales, como por ejemplo
para indicar errores.
class MiExcp extends Exception {
}
Para explicar su funcionamiento, supongamos que se desea ejecutar la instrucción C si el ciclo termina porque se cumple la condición del if, pero no la instrucción B. Y si el ciclo termina porque no se cumple la condición del while, se debe ejecutar B y no C. Entonces, esto se puede programar con throw:
Ahora supongamos que el ciclo tiene varias salidas posibles y en cada
caso se deben ejecutar instrucciones distintas. Entonces se pueden usar
varias excepciones diferentes como en el siguiente ejemplo:
...
try {
while ( ... ) {
...
if ( ... )
throw new MiExcp();
... instrucción A ...
}
... instrucción B ...
}
catch(MiExcp e) {
... instrucción C ...
}
...
try {
while ( ... ) {
...
if ( ... )
throw new MiExcpX();
...
if ( ... )
throw new MiExcpY();
...
}
... instrucción B ...
}
catch(MiExcpX e) {
... instrucción X ...
}
catch(MiExcpY e) {
... instrucción Y ...
}
Cuando dentro de un método es posible que se lanze una excepción,
el encabezado del método debe especificar el nombre de la excepción
mediante la cláusula throws.
int buscar(int x, int[] a) throws NoEncontrado {
for (int i= 0; i<a.length; i++)
if (x==a[i])
return i;
throw new NoEncontrado();
}
Observe que si el entero no es encontrado el método no retorna -1. En ese caso lanza una excepción que puede ser capturada en el método que lo llamó:
A veces no es cómodo manejar la excepción en el método que llamó a buscar.
En ese caso, este debe especificar la excepción con throws:
int[] a= ...;
try {
int pos= buscar(2001, a);
System.out.println("Su posicion es: "+pos);
}
catch (NoEncontrado e) {
System.out.println("no se encontro");
}
Y así el que llama a llamaABuscar puede capturar esa excepción
con un try ... catch o bien especificarla a su vez en el encabezado.
void llamaABuscar() throws NoEncontrado {
int[] a= ...;
int pos= buscar(2001, a);
System.out.println("Su posicion es: "+pos);
}
En el encabezado de un método es obligatorio especificar todas
las excepciones que podrían ser lanzadas directamente por ese
método o indirectamente desde otro método invocado por el primero.
Esto se hace con:
Sin embargo, hay excepciones que no es necesario declarar en el encabezado
de un método porque se supone que se relacionan más bien con errores
de programación y no por causas ajenas al programa. Estas excepciones
son todas las subclases de RuntimeException. Por ejemplo:
NullPointerException, ClassCastException, ArrayIndexOutOfBoundsException,
ArithmeticException, NumberFormatException (la que lanza
Integer.parseInt). Y también las subclases de Error, como
OutOfMemoryError (se acabó la memoria) y StackOverflowError (se acaba
la memoria del stack debido a una recursividad infinita).
... metodo( ... ) throws Excp1, Excp2, ... {
...
}
Ahora el método retorna normalmente si se recorre todo el arreglo
y no se encuentra x. La clase Encontrado debe declararse como:
void descartar(int x, int[] a) throws Encontrado {
for (int i= 0; i<a.length; i++)
if (x==a[i])
throw new Encontrado(i);
}
El siguiente código muestra cómo se usa el método descartar:
class Encontrado extends Exception {
int pos;
Encontrado(int pos) { this.pos= pos; }
}
Observe que el objeto que fue lanzado se captura en la variable que
aparece en el catch. En realidad, lo que apararece entre paréntesis
en el catch es como la declaración de un parámetro en el encabezado de
un método. En tiempo de ejecución el parámetro es el objeto lanzado.
int[] a= ...;
try {
descartar(2001, x);
System.out.println("Correcto");
}
catch (Encontrado e) {
System.out.println("Error, se encontro en la posición "+e.pos);
}
El siguiente es un patrón de lectura de un archivo en disco:
En el código anterior, las expresiones marcadas
en negritas pueden lanzar excepciones. Consulte la API
de Java para averiguar que excepciones puede lanzar un método dado,
o algún constructor.
String nomArch= ...;
try {
BufferedReader lect= new BufferedReader(new FileReader(nomArch));
String lin= lect.readLine();
while(lin!=null) {
System.out.println(lin);
lin= lect.readLine();
}
}
catch (FileNotFoundException e) {
System.err.println("Lo siento, no se encontro "+nomArch);
}
catch (IOException e) {
System.err.println("Error de lectura en el archivo "+nomArch);
}
Ejercicio: Averigue que excepción puede lanzar el constructor de la clase URL en el paquete java.net. Un URL significa Uniform Resource Locator y corresponde a las direcciones que Ud. escribe en la barra del navegador de Internet, como por ejemplo: http://www.dcc.uchile.cl/~lmateu.
Es importante notar que en Java la clase FileNotFoundException es una subclase de IOException. Cuando se lanza una excepción se busca en el método actual si hay algún try ... catch en ejecución. Si lo hay se revisan secuencialmente los catch para ver si el objecto lanzado es una instancia de la clase declarada en cada catch. Si no hay un try en ejecución o no se encuentra ninguna clase apropiada entonces se hace lo mismo en el método llamador. Y así hasta encontrar el catch que captura la excepción o hasta llegar al main. En este último caso se muestra en pantalla la lista de métodos invocados en el momento que se lanzó la excepción.
Entonces, es muy importante no alterar el orden de los catch. En el ejemplo, si se escribe:
El compilador va a reclamar porque el segundo catch nunca podrá
capturar ninguna excepción. Si se lanzara un objeto de la clase
FileNotFoundException, sería capturado por el catch de IOException,
porque el objeto también es instancia de esa clase.
try {
...
}
catch (IOException e) {
System.err.println("Error de lectura en el archivo "+nomArch);
}
catch (FileNotFoundException e) {
System.err.println("Lo siento, no se encontro "+nomArch);
}
Por último, fijese bien en que las instrucciones que lanzan excepciones estén dentro del try. En el siguiente ejemplo, la IOException lanzada en (A) no es capturada por el try programado, porque se encuentra fuera:
try {
...
}
catch (IOException e) {
System.err.println("Error de lectura en el archivo "+nomArch);
}
lin= lect.readLine(); // (A)