Java Language
Excepciones y manejo de excepciones.
Buscar..
Introducción
Throwable
y sus subtipos pueden enviarse a la pila con la palabra clave throw
y capturados con try…catch
declaraciones try…catch
.
Sintaxis
void someMethod () emite SomeException {} // declaración de método, obliga a los llamadores a capturar si SomeException es un tipo de excepción comprobada
tratar {
someMethod(); //code that might throw an exception
}
captura (SomeException e) {
System.out.println("SomeException was thrown!"); //code that will run if certain exception (SomeException) is thrown
}
finalmente {
//code that will always run, whether try block finishes or not
}
Atrapando una excepción con try-catch
Se puede capturar y manejar una excepción utilizando la declaración try...catch
. (De hecho, las declaraciones de try
toman otras formas, como se describe en otros ejemplos sobre try...catch...finally
y try-with-resources
).
Prueba-captura con un bloque de captura
La forma más simple se ve así:
try {
doSomething();
} catch (SomeException e) {
handle(e);
}
// next statement
El comportamiento de un simple try...catch
es el siguiente:
- Se ejecutan las sentencias en el bloque
try
. - Si las declaraciones en el bloque
try
no generan ninguna excepción, entonces el control pasa a la siguiente instrucción después deltry...catch
. - Si se lanza una excepción dentro del bloque
try
.- El objeto de excepción se prueba para ver si es una instancia de
SomeException
o un subtipo. - Si es así, entonces la
catch
bloque detectar la excepción:- La variable
e
está vinculada al objeto de excepción. - Se ejecuta el código dentro del bloque
catch
. - Si ese código lanza una excepción, entonces la excepción recién lanzada se propaga en lugar de la original.
- De lo contrario, el control pasa a la siguiente instrucción después del
try...catch
.
- La variable
- Si no lo es, la excepción original continúa propagándose.
- El objeto de excepción se prueba para ver si es una instancia de
Prueba-captura con múltiples capturas
Un try...catch
también puede tener varios bloques de catch
. Por ejemplo:
try {
doSomething();
} catch (SomeException e) {
handleOneWay(e)
} catch (SomeOtherException e) {
handleAnotherWay(e);
}
// next statement
Si hay varios bloques de catch
, se intentan uno por uno comenzando con el primero, hasta que se encuentra una coincidencia para la excepción. El controlador correspondiente se ejecuta (como anteriormente) y luego el control se pasa a la siguiente instrucción después de la instrucción try...catch
. Los bloques de catch
posteriores a los que coinciden siempre se omiten, incluso si el código del manejador lanza una excepción .
La estrategia de coincidencia "de arriba abajo" tiene consecuencias para los casos en que las excepciones en los bloques catch
no son desunidas. Por ejemplo:
try {
throw new RuntimeException("test");
} catch (Exception e) {
System.out.println("Exception");
} catch (RuntimeException e) {
System.out.println("RuntimeException");
}
Este fragmento de código generará "Exception" en lugar de "RuntimeException". Dado que RuntimeException
es un subtipo de Exception
, la primera catch
(más general) coincidirá. La segunda catch
(más específica) nunca será ejecutada.
La lección para aprender de esto es que los bloques de catch
más específicos (en términos de los tipos de excepción) deben aparecer primero, y los más generales deben ser los últimos. (Algunos compiladores de Java le avisarán si una catch
nunca puede ejecutarse, pero esto no es un error de compilación).
Bloques de captura multi-excepción
A partir de Java SE 7, un solo bloque catch
puede manejar una lista de excepciones no relacionadas. El tipo de excepción se enumera, separado por un símbolo de barra vertical ( |
). Por ejemplo:
try {
doSomething();
} catch (SomeException | SomeOtherException e) {
handleSomeException(e);
}
El comportamiento de una captura de múltiples excepciones es una extensión simple para el caso de excepción única. La catch
coincide si la excepción lanzada coincide (al menos) con una de las excepciones enumeradas.
Hay alguna sutileza adicional en la especificación. El tipo de e
es una unión sintética de los tipos de excepción en la lista. Cuando se utiliza el valor de e
, su tipo estático es el supertipo menos común de la unión de tipos. Sin embargo, si e
es rethrown dentro del bloque catch
, los tipos de excepción que se lanzan son los tipos en la unión. Por ejemplo:
public void method() throws IOException, SQLException
try {
doSomething();
} catch (IOException | SQLException e) {
report(e);
throw e;
}
En lo anterior, IOException
y SQLException
son excepciones revisadas cuyo supertipo menos común es Exception
. Esto significa que el método de report
debe coincidir con el report(Exception)
. Sin embargo, el compilador sabe que el throw
puede lanzar solamente una IOException
o un SQLException
. Por lo tanto, el method
se puede declarar como throws IOException, SQLException
lugar de throws Exception
. (Lo que es bueno: vea Pitfall - Lanzar Throwable, Exception, Error o RuntimeException ).
Lanzar una excepción
El siguiente ejemplo muestra los conceptos básicos de lanzar una excepción:
public void checkNumber(int number) throws IllegalArgumentException {
if (number < 0) {
throw new IllegalArgumentException("Number must be positive: " + number);
}
}
La excepción es lanzada en la tercera línea. Esta declaración se puede dividir en dos partes:
new IllegalArgumentException(...)
crea una instancia de la claseIllegalArgumentException
, con un mensaje que describe el error que informa la excepción.throw ...
es lanzar el objeto de excepción.
Cuando se lanza la excepción, hace que las declaraciones adjuntas terminen de forma anormal hasta que se maneje la excepción. Esto se describe en otros ejemplos.
Es una buena práctica crear y lanzar el objeto de excepción en una sola declaración, como se muestra arriba. También es una buena práctica incluir un mensaje de error significativo en la excepción para ayudar al programador a comprender la causa del problema. Sin embargo, este no es necesariamente el mensaje que debe mostrar al usuario final. (Para empezar, Java no tiene soporte directo para internacionalizar mensajes de excepción).
Hay un par de puntos más por hacer:
Hemos declarado el
checkNumber
comothrows IllegalArgumentException
. Esto no fue estrictamente necesario, ya queIllegalArgumentException
es una excepción comprobada; vea La jerarquía de excepciones de Java - Excepciones no verificadas y revisadas . Sin embargo, es una buena práctica hacer esto, y también incluir las excepciones lanzadas por los comentarios javadoc de un método.El código inmediatamente después de una declaración de
throw
es inalcanzable . Por lo tanto si escribimos esto:throw new IllegalArgumentException("it is bad"); return;
el compilador informaría un error de compilación para la declaración de
return
.
Encadenamiento de excepciones
Muchas excepciones estándar tienen un constructor con un segundo argumento de cause
además del argumento de message
convencional. La cause
permite encadenar excepciones. Aquí hay un ejemplo.
Primero definimos una excepción no verificada que nuestra aplicación va a lanzar cuando encuentra un error no recuperable. Tenga en cuenta que hemos incluido un constructor que acepta un argumento de cause
.
public class AppErrorException extends RuntimeException {
public AppErrorException() {
super();
}
public AppErrorException(String message) {
super(message);
}
public AppErrorException(String message, Throwable cause) {
super(message, cause);
}
}
A continuación, aquí hay un código que ilustra el encadenamiento de excepciones.
public String readFirstLine(String file) throws AppErrorException {
try (Reader r = new BufferedReader(new FileReader(file))) {
String line = r.readLine();
if (line != null) {
return line;
} else {
throw new AppErrorException("File is empty: " + file);
}
} catch (IOException ex) {
throw new AppErrorException("Cannot read file: " + file, ex);
}
}
El throw
dentro del bloque try
detecta un problema y lo informa a través de una excepción con un mensaje simple. Por el contrario, el throw
dentro del bloque catch
está manejando la IOException
envolviéndolo en una nueva excepción (marcada). Sin embargo, no está tirando la excepción original. Al pasar la IOException
como cause
, la grabamos para que pueda imprimirse en el seguimiento de pila, como se explica en Creación y lectura de los registros de pila .
Excepciones personalizadas
En la mayoría de los casos, es más simple desde el punto de vista de diseño de código usar clases de Exception
genéricas existentes al lanzar excepciones. Esto es especialmente cierto si solo necesita la excepción para llevar un simple mensaje de error. En ese caso, generalmente se prefiere RuntimeException , ya que no es una excepción marcada. Existen otras clases de excepción para las clases comunes de errores:
- UnsupportedOperationException : una determinada operación no es compatible
- IllegalArgumentException : se pasó un valor de parámetro no válido a un método
- IllegalStateException : su API ha alcanzado internamente una condición que nunca debería ocurrir, o que se produce como resultado de usar su API de forma no válida
Casos en los que usted desee utilizar una clase de excepción personalizada incluyen los siguientes:
- Está escribiendo una API o biblioteca para que otros la utilicen, y quiere permitir que los usuarios de su API puedan capturar y manejar específicamente las excepciones de su API y poder diferenciar esas excepciones de otras excepciones más genéricas .
- Está lanzando excepciones para un tipo específico de error en una parte de su programa, que desea detectar y manejar en otra parte de su programa, y desea poder diferenciar estos errores de otros errores más genéricos.
Puede crear sus propias excepciones personalizadas extendiendo RuntimeException
para una excepción no verificada, o excepción comprobada extendiendo cualquier Exception
que no sea también subclase de RuntimeException , porque:
Las subclases de Excepción que no son también subclases de RuntimeException son excepciones comprobadas
public class StringTooLongException extends RuntimeException {
// Exceptions can have methods and fields like other classes
// those can be useful to communicate information to pieces of code catching
// such an exception
public final String value;
public final int maximumLength;
public StringTooLongException(String value, int maximumLength){
super(String.format("String exceeds maximum Length of %s: %s", maximumLength, value));
this.value = value;
this.maximumLength = maximumLength;
}
}
Se pueden usar solo como excepciones predefinidas:
void validateString(String value){
if (value.length() > 30){
throw new StringTooLongException(value, 30);
}
}
Y los campos se pueden usar donde se captura y maneja la excepción:
void anotherMethod(String value){
try {
validateString(value);
} catch(StringTooLongException e){
System.out.println("The string '" + e.value +
"' was longer than the max of " + e.maximumLength );
}
}
Tenga en cuenta que, según la documentación de Java de Oracle :
[...] Si es razonable esperar que un cliente se recupere de una excepción, conviértalo en una excepción comprobada. Si un cliente no puede hacer nada para recuperarse de la excepción, conviértalo en una excepción sin marcar.
Más:
La declaración de prueba con recursos
Como lo ilustra el ejemplo de la sentencia try-catch-final , la limpieza de recursos usando una cláusula finally
requiere una cantidad significativa de código de "placa de caldera" para implementar los casos de borde correctamente. Java 7 proporciona una forma mucho más sencilla de resolver este problema en la forma de la declaración try-with-resources .
¿Qué es un recurso?
Java 7 introdujo la interfaz java.lang.AutoCloseable
para permitir que las clases se administren usando la declaración try-with-resources . Las instancias de clases que implementan AutoCloseable
se conocen como recursos . Por lo general, estos deben eliminarse de manera oportuna en lugar de confiar en el recolector de basura para eliminarlos.
AutoCloseable
interfaz AutoCloseable
define un solo método:
public void close() throws Exception
Un método close()
debe disponer del recurso de una manera apropiada. La especificación establece que debería ser seguro llamar al método en un recurso que ya se ha eliminado. Además, se recomienda encarecidamente a las clases que implementan el Autocloseable
que declaren el método close()
para lanzar una excepción más específica que la Exception
, o ninguna excepción en absoluto.
Una amplia gama de clases e interfaces Java estándar implementan AutoCloseable
. Éstos incluyen:
-
InputStream
,OutputStream
y sus subclases -
Reader
,Writer
y sus subclases. -
Socket
yServerSocket
y sus subclases -
Channel
y sus subclases, y - Las interfaces JDBC
Connection
,Statement
yResultSet
y sus subclases.
La aplicación y las clases de terceros pueden hacer esto también.
La declaración básica de prueba con recursos.
La sintaxis de un try-with-resources se basa en las formas clásicas try-catch , try-finally y try-catch-finally . Aquí hay un ejemplo de una forma "básica"; Es decir, la forma sin catch
o finally
.
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
}
Los recursos a administrar se declaran como variables en la sección (...)
después de la cláusula try
. En el ejemplo anterior, declaramos un stream
variable de recurso y lo inicializamos a un PrintStream
recién creado.
Una vez que las variables de recursos se han inicializado, se ejecuta el bloque try
. Cuando esto se complete, se stream.close()
automáticamente para garantizar que el recurso no se escape. Tenga en cuenta que la llamada close()
ocurre sin importar cómo se complete el bloque.
Las declaraciones mejoradas de prueba con recursos
La instrucción try-with-resources puede mejorarse con catch
bloqueos catch
y finally
, como con la sintaxis try-catch-finally pre-Java 7. El siguiente fragmento de código agrega un bloque catch
a nuestro anterior para tratar con la FileNotFoundException
que el constructor PrintStream
puede lanzar:
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
} catch (FileNotFoundException ex) {
System.err.println("Cannot open the file");
} finally {
System.err.println("All done");
}
Si la inicialización del recurso o el bloque try lanzan la excepción, entonces se ejecutará el bloque catch
. El bloque finally
siempre se ejecutará, como con una sentencia convencional try-catch-finally .
Hay un par de cosas a tener en cuenta, sin embargo:
- La variable de recurso está fuera de alcance en los bloqueos de
catch
yfinally
. - La limpieza de recursos se realizará antes de que la declaración intente coincidir con el bloque
catch
. - Si la limpieza automática de recursos arrojó una excepción, entonces podría quedar atrapado en uno de los bloques de
catch
.
Gestionando múltiples recursos
Los fragmentos de código anteriores muestran un solo recurso que se está administrando. De hecho, try-with-resources puede administrar múltiples recursos en una sola declaración. Por ejemplo:
try (InputStream is = new FileInputStream(file1);
OutputStream os = new FileOutputStream(file2)) {
// Copy 'is' to 'os'
}
Esto se comporta como cabría esperar. Ambos is
y os
se cierran automáticamente al final del bloque try
. Hay un par de puntos a tener en cuenta:
- Las inicializaciones se producen en el orden del código, y los inicializadores de variables de recursos posteriores pueden utilizar los valores de los anteriores.
- Todas las variables de recursos que se inicializaron correctamente se limpiarán.
- Las variables de recursos se limpian en orden inverso a sus declaraciones.
Por lo tanto, en el ejemplo anterior, is
inicializa antes del sistema os
y se limpia después de él, y is
limpiará si hay una excepción al inicializar el sistema os
.
Equivalencia de try-with-resource y clásico try-catch-finally
La especificación del lenguaje Java especifica el comportamiento de los formularios de prueba con recursos en términos de la declaración clásica de prueba-captura- final. (Consulte los detalles completos en el JLS.)
Por ejemplo, este intento básico con el recurso :
try (PrintStream stream = new PrintStream("hello.txt")) {
stream.println("Hello world!");
}
se define como equivalente a este try-catch-finally :
// Note that the constructor is not part of the try-catch statement
PrintStream stream = new PrintStream("hello.txt");
// This variable is used to keep track of the primary exception thrown
// in the try statement. If an exception is thrown in the try block,
// any exception thrown by AutoCloseable.close() will be suppressed.
Throwable primaryException = null;
// The actual try block
try {
stream.println("Hello world!");
} catch (Throwable t) {
// If an exception is thrown, remember it for the finally block
primaryException = t;
throw t;
} finally {
if (primaryException == null) {
// If no exception was thrown so far, exceptions thrown in close() will
// not be caught and therefore be passed on to the enclosing code.
stream.close();
} else {
// If an exception has already been thrown, any exception thrown in
// close() will be suppressed as it is likely to be related to the
// previous exception. The suppressed exception can be retrieved
// using primaryException.getSuppressed().
try {
stream.close();
} catch (Throwable suppressedException) {
primaryException.addSuppressed(suppressedException);
}
}
}
(El JLS especifica que las variables t
y primaryException
reales serán invisibles para el código Java normal).
La forma mejorada de try-with-resources se especifica como una equivalencia con la forma básica. Por ejemplo:
try (PrintStream stream = new PrintStream(fileName)) {
stream.println("Hello world!");
} catch (NullPointerException ex) {
System.err.println("Null filename");
} finally {
System.err.println("All done");
}
es equivalente a:
try {
try (PrintStream stream = new PrintStream(fileName)) {
stream.println("Hello world!");
}
} catch (NullPointerException ex) {
System.err.println("Null filename");
} finally {
System.err.println("All done");
}
Creación y lectura de stacktraces.
Cuando se crea un objeto de excepción (es decir, cuando se crea una new
), el constructor Throwable
captura información sobre el contexto en el que se creó la excepción. Más adelante, esta información se puede generar en forma de stacktrace, que puede usarse para ayudar a diagnosticar el problema que causó la excepción en primer lugar.
Imprimiendo un stacktrace
Imprimir un stacktrace es simplemente una cuestión de llamar al método printStackTrace()
. Por ejemplo:
try {
int a = 0;
int b = 0;
int c = a / b;
} catch (ArithmeticException ex) {
// This prints the stacktrace to standard output
ex.printStackTrace();
}
El método printStackTrace()
sin argumentos se imprimirá en la salida estándar de la aplicación; Es decir, el actual System.out
. También hay printStackTrace(PrintStream)
e printStackTrace(PrintWriter)
que se imprimen en un Stream
o Writer
específico.
Notas:
El stacktrace no incluye los detalles de la excepción en sí. Puedes usar el método
toString()
para obtener esos detalles; p.ej// Print exception and stacktrace System.out.println(ex); ex.printStackTrace();
La impresión Stacktrace debe usarse con moderación; ver Escapada - Stacktraces excesivos o inapropiados . A menudo es mejor usar un marco de registro y pasar el objeto de excepción que se va a registrar.
Entendiendo un stacktrace
Considere el siguiente programa simple que consiste en dos clases en dos archivos. (Hemos mostrado los nombres de archivo y los números de línea agregados con fines ilustrativos).
File: "Main.java"
1 public class Main {
2 public static void main(String[] args) {
3 new Test().foo();
4 }
5 }
File: "Test.java"
1 class Test {
2 public void foo() {
3 bar();
4 }
5
6 public int bar() {
7 int a = 1;
8 int b = 0;
9 return a / b;
10 }
Cuando estos archivos se compilan y ejecutan, obtendremos el siguiente resultado.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Test.bar(Test.java:9)
at Test.foo(Test.java:3)
at Main.main(Main.java:3)
Vamos a leer esta línea a la vez para averiguar lo que nos está diciendo.
La línea # 1 nos dice que el hilo llamado "main" ha terminado debido a una excepción no detectada. El nombre completo de la excepción es java.lang.ArithmeticException
, y el mensaje de excepción es "/ by zero".
Si buscamos los javadocs para esta excepción, dice:
Se lanza cuando ha ocurrido una condición aritmética excepcional. Por ejemplo, un entero "dividir por cero" lanza una instancia de esta clase.
De hecho, el mensaje "/ por cero" es un fuerte indicio de que la causa de la excepción es que algún código ha intentado dividir algo entre cero. ¿Pero que?
Las 3 líneas restantes son la traza de pila. Cada línea representa una llamada de método (o constructor) en la pila de llamadas, y cada una nos dice tres cosas:
- el nombre de la clase y el método que se estaba ejecutando,
- el nombre del archivo de código fuente,
- el número de línea del código fuente de la sentencia que se estaba ejecutando
Estas líneas de un seguimiento de pila se enumeran con el marco para la llamada actual en la parte superior. El cuadro superior en nuestro ejemplo anterior está en el método Test.bar
y en la línea 9 del archivo Test.java. Esa es la siguiente línea:
return a / b;
Si observamos un par de líneas en el archivo donde se inicializa b
, es evidente que b
tendrá el valor cero. Podemos decir sin ninguna duda que esta es la causa de la excepción.
Si quisiéramos ir más lejos, podemos ver que desde el StackTrace bar()
fue llamado desde foo()
en la línea 3 de Test.java, y que foo()
a su vez fue llamado desde Main.main()
.
Nota: Los nombres de clase y método en los marcos de pila son los nombres internos de las clases y métodos. Tendrá que reconocer los siguientes casos inusuales:
- Una clase anidada o interna se verá como "OuterClass $ InnerClass".
- Una clase interna anónima se verá como "Clase externa $ 1", "Clase externa $ 2", etc.
- Cuando se ejecuta el código en un constructor, el inicializador de campo de instancia o un bloque de inicializador de instancia, el nombre del método será "".
- Cuando se ejecuta el código en un inicializador de campo estático o en un bloque de inicializador estático, el nombre del método será "".
(En algunas versiones de Java, el código de formato stacktrace detectará y eliminará las secuencias repetidas de cuadros de pila, como puede ocurrir cuando una aplicación falla debido a una recursión excesiva).
Excepción de encadenamiento y stacktraces anidados.
El encadenamiento de excepciones ocurre cuando un fragmento de código atrapa una excepción, y luego crea y lanza uno nuevo, pasando la primera excepción como la causa. Aquí hay un ejemplo:
File: Test,java
1 public class Test {
2 int foo() {
3 return 0 / 0;
4 }
5
6 public Test() {
7 try {
8 foo();
9 } catch (ArithmeticException ex) {
10 throw new RuntimeException("A bad thing happened", ex);
11 }
12 }
13
14 public static void main(String[] args) {
15 new Test();
16 }
17 }
Cuando la clase anterior se compila y ejecuta, obtenemos el siguiente seguimiento de pila:
Exception in thread "main" java.lang.RuntimeException: A bad thing happened
at Test.<init>(Test.java:10)
at Test.main(Test.java:15)
Caused by: java.lang.ArithmeticException: / by zero
at Test.foo(Test.java:3)
at Test.<init>(Test.java:8)
... 1 more
El stacktrace comienza con el nombre de la clase, el método y la pila de llamadas para la excepción que (en este caso) causó que la aplicación fallara. A esto le sigue una línea "Causado por:" que informa la excepción de cause
. Se informa el nombre de la clase y el mensaje, seguidos de los marcos de pila de la excepción de causa. La traza finaliza con un "... N más" que indica que los últimos N cuadros son los mismos que para la excepción anterior.
El "Causado por:" solo se incluye en la salida cuando la cause
la excepción principal no es null
). Las excepciones se pueden encadenar indefinidamente, y en ese caso, el seguimiento de pila puede tener múltiples trazas de "Causado por:".
Nota: el mecanismo de cause
solo se expuso en la API de Throwable
en Java 1.4.0. Antes de eso, era necesario que la aplicación implementara el encadenamiento de excepciones utilizando un campo de excepción personalizado para representar la causa y un método personalizado de printStackTrace
.
Capturando un stacktrace como una cadena
A veces, una aplicación necesita poder capturar un stacktrace como una String
Java, para que pueda ser usada para otros propósitos. El enfoque general para hacer esto es crear un OutputStream
o Writer
temporal que escribe en un búfer en memoria y se lo pasa a printStackTrace(...)
.
Las bibliotecas Apache Commons y Guava proporcionan métodos de utilidad para capturar un stacktrace como una cadena:
org.apache.commons.lang.exception.ExceptionUtils.getStackTrace(Throwable)
com.google.common.base.Throwables.getStackTraceAsString(Throwable)
Si no puede usar bibliotecas de terceros en su base de código, entonces el siguiente método hace la tarea:
/**
* Returns the string representation of the stack trace.
*
* @param throwable the throwable
* @return the string.
*/
public static String stackTraceToString(Throwable throwable) {
StringWriter stringWriter = new StringWriter();
throwable.printStackTrace(new PrintWriter(stringWriter));
return stringWriter.toString();
}
Tenga en cuenta que si su intención es analizar el seguimiento de pila, es más sencillo utilizar getStackTrace()
y getCause()
que intentar analizar un seguimiento de pila.
Manipulación InterruptedException
InterruptedException
es una bestia confusa: aparece en métodos aparentemente inocuos como Thread.sleep()
, pero su manejo incorrecto conduce a un código difícil de administrar que se comporta mal en entornos concurrentes.
En su forma más básica, si se Thread.interrupt()
una InterruptedException
, significa alguien, en algún lugar, llamado Thread.interrupt()
en el hilo en el que se está ejecutando su código. Es posible que esté inclinado a decir "¡Es mi código! ¡Nunca lo interrumpiré! " y por lo tanto hacer algo como esto:
// Bad. Don't do this.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// disregard
}
Pero esta es exactamente la manera incorrecta de manejar un evento "imposible" que ocurre. Si sabe que su aplicación nunca encontrará una InterruptedException
, debe tratar dicho evento como una violación grave de las suposiciones de su programa y salir lo más rápido posible.
La forma correcta de manejar una interrupción "imposible" es así:
// When nothing will interrupt your code
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new AssertionError(e);
}
Esto hace dos cosas; primero restaura el estado de interrupción del hilo (como si la InterruptedException
no se hubiera lanzado en primer lugar), y luego lanza un AssertionError
indica que se han violado los invariantes básicos de su aplicación. Si sabe con certeza que nunca interrumpirá el hilo en el que se ejecuta este código es seguro, ya que nunca debe catch
bloque catch
.
El uso de la clase Uninterruptibles
de Guava ayuda a simplificar este patrón; llamando a Uninterruptibles.sleepUninterruptibly()
ignora el estado interrumpido de un hilo hasta que la duración del tiempo de espera (en ese momento se restaura para las llamadas posteriores para inspeccionar y lanzar su propia InterruptedException
). Si sabe que nunca interrumpirá dicho código, esto evita con seguridad la necesidad de ajustar sus llamadas de reposo en un bloque try-catch.
Más a menudo, sin embargo, no puede garantizar que su hilo nunca será interrumpido. En particular, si está escribiendo un código que será ejecutado por un Executor
o algún otro administrador de subprocesos, es fundamental que su código responda rápidamente a las interrupciones, de lo contrario su aplicación se atascará o incluso se interrumpirá.
En tales casos, lo mejor es generalmente permitir que la InterruptedException
propague por la pila de llamadas, agregando una throws InterruptedException
a cada método. Esto puede parecer un poco confuso, pero en realidad es una propiedad deseable: las firmas de su método ahora indican a las personas que llaman que responderá rápidamente a las interrupciones.
// Let the caller determine how to handle the interrupt if you're unsure
public void myLongRunningMethod() throws InterruptedException {
...
}
En casos limitados (p. Ej., Al anular un método que no throw
ninguna excepción comprobada), puede restablecer el estado interrumpido sin generar una excepción, esperando que se ejecute cualquier código al lado para manejar la interrupción. Esto retrasa el manejo de la interrupción pero no la suprime por completo.
// Suppresses the exception but resets the interrupted state letting later code
// detect the interrupt and handle it properly.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return ...; // your expectations are still broken at this point - try not to do more work.
}
La jerarquía de excepciones de Java - Excepciones no verificadas y revisadas
Todas las excepciones de Java son instancias de clases en la jerarquía de clases de Excepción. Esto se puede representar de la siguiente manera:
-
java.lang.Throwable
: esta es la clase base para todas las clases de excepción. Sus métodos y constructores implementan una gama de funcionalidades comunes a todas las excepciones.-
java.lang.Exception
: esta es la superclase de todas las excepciones normales.- Diversas clases de excepción estándar y personalizadas.
-
java.lang.RuntimeException
- Esta es la superclase de todas las excepciones normales que son excepciones sin marcar .- Varias clases de excepción de tiempo de ejecución estándar y personalizadas.
-
java.lang.Error
: esta es la superclase de todas las excepciones de "error fatal".
-
Notas:
- La distinción entre excepciones marcadas y no marcadas se describe a continuación.
- La clase
Throwable
,Exception
yRuntimeException
debe tratar comoabstract
; ver Pitfall - Lanzar Throwable, Exception, Error o RuntimeException . - La JVM lanza las excepciones de
Error
en situaciones en las que sería inseguro o imprudente que una aplicación intentara recuperarse. - No sería prudente declarar subtipos personalizados de
Throwable
. Las herramientas y bibliotecas de Java pueden asumir queError
yException
son los únicos subtipos directos deThrowable
, y se comportan mal si esa suposición es incorrecta.
Excepciones marcadas versus no verificadas
Una de las críticas de la compatibilidad con excepciones en algunos lenguajes de programación es que es difícil saber qué excepciones puede dar un método o procedimiento determinado. Dado que una excepción no controlada puede provocar que un programa se bloquee, esto puede hacer que las excepciones sean una fuente de fragilidad.
El lenguaje Java aborda esta preocupación con el mecanismo de excepción verificado. Primero, Java clasifica las excepciones en dos categorías:
Las excepciones verificadas típicamente representan eventos anticipados con los que una aplicación debería poder lidiar. Por ejemplo,
IOException
y sus subtipos representan condiciones de error que pueden ocurrir en operaciones de E / S. Los ejemplos incluyen, el archivo se abre porque falla porque no existe un archivo o directorio, la red lee y escribe fallas porque una conexión de red se ha roto y así sucesivamente.Las excepciones no verificadas típicamente representan eventos no anticipados con los que una aplicación no puede lidiar. Estos suelen ser el resultado de un error en la aplicación.
(En lo siguiente, "lanzado" se refiere a cualquier excepción lanzada explícitamente (por una declaración de throw
), o implícitamente (en una desreferencia fallida, tipo de conversión y así sucesivamente). De manera similar, "propagada" se refiere a una excepción que fue lanzada en una llamada anidada, y no atrapada dentro de esa llamada. El siguiente código de ejemplo lo ilustrará.)
La segunda parte del mecanismo de excepción comprobada es que existen restricciones en los métodos en los que se puede producir una excepción comprobada:
- Cuando una excepción marcada es lanzada o propagada en un método, debe ser capturada por el método o listada en la cláusula de
throws
del método. (El significado de la cláusula dethrows
se describe en este ejemplo ). - Cuando se lanza o se propaga una excepción marcada en un bloque de inicialización, se debe capturar el bloque.
- Una excepción comprobada no puede propagarse mediante una llamada de método en una expresión de inicialización de campo. (No hay manera de atrapar tal excepción.)
En resumen, una excepción marcada debe ser manejada o declarada.
Estas restricciones no se aplican a las excepciones sin marcar. Esto incluye todos los casos en los que se lanza una excepción implícitamente, ya que todos los casos arrojan excepciones no verificadas.
Ejemplos de excepciones verificadas
Estos fragmentos de código pretenden ilustrar las restricciones de excepción comprobadas. En cada caso, mostramos una versión del código con un error de compilación, y una segunda versión con el error corregido.
// This declares a custom checked exception.
public class MyException extends Exception {
// constructors omitted.
}
// This declares a custom unchecked exception.
public class MyException2 extends RuntimeException {
// constructors omitted.
}
El primer ejemplo muestra cómo las excepciones comprobadas lanzadas explícitamente se pueden declarar como "lanzadas" si no se deben manejar en el método.
// INCORRECT
public void methodThrowingCheckedException(boolean flag) {
int i = 1 / 0; // Compiles OK, throws ArithmeticException
if (flag) {
throw new MyException(); // Compilation error
} else {
throw new MyException2(); // Compiles OK
}
}
// CORRECTED
public void methodThrowingCheckedException(boolean flag) throws MyException {
int i = 1 / 0; // Compiles OK, throws ArithmeticException
if (flag) {
throw new MyException(); // Compilation error
} else {
throw new MyException2(); // Compiles OK
}
}
El segundo ejemplo muestra cómo se puede tratar una excepción comprobada propagada.
// INCORRECT
public void methodWithPropagatedCheckedException() {
InputStream is = new FileInputStream("someFile.txt"); // Compilation error
// FileInputStream throws IOException or a subclass if the file cannot
// be opened. IOException is a checked exception.
...
}
// CORRECTED (Version A)
public void methodWithPropagatedCheckedException() throws IOException {
InputStream is = new FileInputStream("someFile.txt");
...
}
// CORRECTED (Version B)
public void methodWithPropagatedCheckedException() {
try {
InputStream is = new FileInputStream("someFile.txt");
...
} catch (IOException ex) {
System.out.println("Cannot open file: " + ex.getMessage());
}
}
El último ejemplo muestra cómo lidiar con una excepción marcada en un inicializador de campo estático.
// INCORRECT
public class Test {
private static final InputStream is =
new FileInputStream("someFile.txt"); // Compilation error
}
// CORRECTED
public class Test {
private static final InputStream is;
static {
InputStream tmp = null;
try {
tmp = new FileInputStream("someFile.txt");
} catch (IOException ex) {
System.out.println("Cannot open file: " + ex.getMessage());
}
is = tmp;
}
}
Nótese que en este último caso, también tenemos que hacer frente a los problemas que is
no se pueden asignar a más de una vez, y sin embargo, también tiene que ser asignado a, incluso en el caso de una excepción.
Introducción
Las excepciones son errores que ocurren cuando un programa se está ejecutando. Considere el siguiente programa Java que divide dos enteros.
class Division {
public static void main(String[] args) {
int a, b, result;
Scanner input = new Scanner(System.in);
System.out.println("Input two integers");
a = input.nextInt();
b = input.nextInt();
result = a / b;
System.out.println("Result = " + result);
}
}
Ahora compilamos y ejecutamos el código anterior, y vemos el resultado de un intento de división por cero:
Input two integers
7 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Division.main(Disivion.java:14)
La división por cero es una operación no válida que produciría un valor que no se puede representar como un entero. Java se ocupa de esto lanzando una excepción . En este caso, la excepción es una instancia de la clase ArithmeticException .
Nota: el ejemplo sobre la creación y lectura de seguimientos de pila explica qué significa la salida después de los dos números.
La utilidad de una excepción es el control de flujo que permite. Sin usar excepciones, una solución típica a este problema puede ser verificar primero si b == 0
:
class Division {
public static void main(String[] args) {
int a, b, result;
Scanner input = new Scanner(System.in);
System.out.println("Input two integers");
a = input.nextInt();
b = input.nextInt();
if (b == 0) {
System.out.println("You cannot divide by zero.");
return;
}
result = a / b;
System.out.println("Result = " + result);
}
}
Esto imprime el mensaje You cannot divide by zero.
a la consola y abandona el programa de una manera elegante cuando el usuario intenta dividir por cero. Una forma equivalente de resolver este problema mediante el manejo de excepciones sería reemplazar el control if
flow con un bloque try-catch
:
...
a = input.nextInt();
b = input.nextInt();
try {
result = a / b;
}
catch (ArithmeticException e) {
System.out.println("An ArithmeticException occurred. Perhaps you tried to divide by zero.");
return;
}
...
Un bloque try catch se ejecuta de la siguiente manera:
- Comienza a ejecutar el código en el bloque
try
. - Si se produce una excepción en el bloque try, abortar inmediatamente y comprobar para ver si esta excepción es capturado por la
catch
de bloque (en este caso, cuando la excepción es una instancia deArithmeticException
). - Si se captura la excepción, se asigna a la variable
e
y se ejecuta el bloquecatch
. - Si se completa el bloque
try
ocatch
(es decir, no se producen excepciones no detectadas durante la ejecución del código), continúe ejecutando el código debajo del bloquetry-catch
.
En general, se considera una buena práctica usar el manejo de excepciones como parte del control de flujo normal de una aplicación donde el comportamiento sería indefinido o inesperado. Por ejemplo, en lugar de devolver un null
cuando falla un método, generalmente es una mejor práctica lanzar una excepción para que la aplicación que hace uso del método pueda definir su propio control de flujo para la situación mediante el manejo de excepciones del tipo ilustrado anteriormente. En cierto sentido, esto soluciona el problema de tener que devolver un tipo particular, ya que se pueden lanzar cualquiera de los múltiples tipos de excepciones para indicar el problema específico que ocurrió.
Para obtener más consejos sobre cómo y cómo no usar excepciones, consulte Desventajas de Java - Uso de excepciones
Declaraciones de retorno en el bloque try catch
Aunque es una mala práctica, es posible agregar varias declaraciones de devolución en un bloque de manejo de excepciones:
public static int returnTest(int number){
try{
if(number%2 == 0) throw new Exception("Exception thrown");
else return x;
}
catch(Exception e){
return 3;
}
finally{
return 7;
}
}
Este método siempre devolverá 7 ya que el bloque finally asociado con el bloque try / catch se ejecuta antes de que se devuelva algo. Ahora, como finalmente ha return 7;
, este valor reemplaza los valores de retorno de prueba / captura.
Si el bloque catch devuelve un valor primitivo y ese valor primitivo se cambia posteriormente en el bloque finally, se devolverá el valor devuelto en el bloque catch y se ignorarán los cambios del bloque finally.
El siguiente ejemplo imprimirá "0", no "1".
public class FinallyExample {
public static void main(String[] args) {
int n = returnTest(4);
System.out.println(n);
}
public static int returnTest(int number) {
int returnNumber = 0;
try {
if (number % 2 == 0)
throw new Exception("Exception thrown");
else
return returnNumber;
} catch (Exception e) {
return returnNumber;
} finally {
returnNumber = 1;
}
}
}
Funciones avanzadas de excepciones
Este ejemplo cubre algunas características avanzadas y casos de uso para excepciones.
Examinando la pila de llamadas programáticamente
El uso principal de los stacktraces de excepción es proporcionar información sobre un error de aplicación y su contexto para que el programador pueda diagnosticar y solucionar el problema. A veces se puede utilizar para otras cosas. Por ejemplo, una clase de SecurityManager
debe examinar la pila de llamadas para decidir si el código que está realizando una llamada debe ser confiable.
Puede usar excepciones para examinar la pila de llamadas programáticamente de la siguiente manera:
Exception ex = new Exception(); // this captures the call stack
StackTraceElement[] frames = ex.getStackTrace();
System.out.println("This method is " + frames[0].getMethodName());
System.out.println("Called from method " + frames[1].getMethodName());
Hay algunas advertencias importantes sobre esto:
La información disponible en un
StackTraceElement
es limitada. No hay más información disponible de la que se muestra enprintStackTrace
. (Los valores de las variables locales en el marco no están disponibles.)Los javadocs para
getStackTrace()
establecen que una JVM puede omitir marcos:Algunas máquinas virtuales pueden, en algunas circunstancias, omitir uno o más marcos de pila de la traza de pila. En el caso extremo, se permite que una máquina virtual que no tenga información de rastreo de pila con respecto a este objeto de lanzamiento devuelva una matriz de longitud cero desde este método.
Optimizando la construcción de excepciones.
Como se mencionó en otra parte, construir una excepción es bastante costoso porque implica capturar y registrar información sobre todos los marcos de pila en el subproceso actual. A veces, sabemos que esa información nunca se utilizará para una excepción determinada; Por ejemplo, el stacktrace nunca se imprimirá. En ese caso, hay un truco de implementación que podemos usar en una excepción personalizada para hacer que la información no sea capturada.
La información del marco de pila necesaria para los stacktraces, se captura cuando los constructores de Throwable
llaman al método Throwable.fillInStackTrace()
. Este método es public
, lo que significa que una subclase puede anularlo. El truco consiste en anular el método heredado de Throwable
con uno que no hace nada; p.ej
public class MyException extends Exception {
// constructors
@Override
public void fillInStackTrace() {
// do nothing
}
}
El problema con este enfoque es que una excepción que reemplaza a fillInStackTrace()
nunca puede capturar el seguimiento de pila, y es inútil en los escenarios en los que necesita uno.
Borrado o sustitución del trazo de pila.
En algunas situaciones, el seguimiento de pila para una excepción creada de manera normal contiene información incorrecta o información que el desarrollador no quiere revelar al usuario. Para estos escenarios, Throwable.setStackTrace
se puede usar para reemplazar la matriz de objetos StackTraceElement
que contiene la información.
Por ejemplo, se puede usar lo siguiente para descartar la información de la pila de una excepción:
exception.setStackTrace(new StackTraceElement[0]);
Excepciones suprimidas
Java 7 introdujo la construcción try-with-resources y el concepto asociado de supresión de excepciones. Considere el siguiente fragmento de código:
try (Writer w = new BufferedWriter(new FileWriter(someFilename))) {
// do stuff
int temp = 0 / 0; // throws an ArithmeticException
}
Cuando se lanza la excepción, el try
llamará a close()
en la w
lo que vaciará cualquier salida con búfer y luego cerrará el FileWriter
. Pero, ¿qué sucede si se lanza una IOException
al vaciar la salida?
Lo que sucede es que se suprime cualquier excepción que se lance al limpiar un recurso. La excepción se captura y se agrega a la lista de excepciones suprimidas de la excepción primaria. A continuación, el try-with-resources continuará con la limpieza de los otros recursos. Finalmente, la excepción primaria será devuelta.
Se produce un patrón similar si se produce una excepción durante la inicialización del recurso, o si el bloque try
se completa normalmente. La primera excepción lanzada se convierte en la excepción principal, y las subsiguientes que surgen de la limpieza se suprimen.
Las excepciones suprimidas se pueden recuperar del objeto de excepción principal llamando a getSuppressedExceptions
.
Las declaraciones try-finally y try-catch-finally
La declaración try...catch...finally
combina el manejo de excepciones con el código de limpieza. El bloque finally
contiene código que se ejecutará en todas las circunstancias. Esto los hace adecuados para la gestión de recursos y otros tipos de limpieza.
Intento-finalmente
Aquí hay un ejemplo de la forma más simple ( try...finally
):
try {
doSomething();
} finally {
cleanUp();
}
El comportamiento del try...finally
es el siguiente:
- Se ejecuta el código en el bloque
try
. - Si no se lanzó ninguna excepción en el bloque
try
:- Se ejecuta el código en el bloque
finally
. - Si el bloque
finally
lanza una excepción, esa excepción se propaga. - De lo contrario, el control pasa a la siguiente declaración después del
try...finally
.
- Se ejecuta el código en el bloque
- Si se lanzó una excepción en el bloque de prueba:
- Se ejecuta el código en el bloque
finally
. - Si el bloque
finally
lanza una excepción, esa excepción se propaga. - De lo contrario, la excepción original continúa propagándose.
- Se ejecuta el código en el bloque
El código dentro de finally
de bloque se ejecutará siempre. (Las únicas excepciones son si System.exit(int)
se llama, o si los pánicos de JVM.) Así pues, un finally
de bloque es el código correcto lugar que siempre necesita ser ejecutada; Por ejemplo, cerrando archivos y otros recursos o liberando bloqueos.
prueba-captura-finalmente
Nuestro segundo ejemplo muestra cómo catch
y finally
pueden usarse juntos. También ilustra que la limpieza de recursos no es sencilla.
// This code snippet writes the first line of a file to a string
String result = null;
Reader reader = null;
try {
reader = new BufferedReader(new FileReader(fileName));
result = reader.readLine();
} catch (IOException ex) {
Logger.getLogger.warn("Unexpected IO error", ex); // logging the exception
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
// ignore / discard this exception
}
}
}
El conjunto completo de comportamientos (hipotéticos) de try...catch...finally
en este ejemplo es demasiado complicado de describir aquí. La versión simple es que el código en el bloque finally
siempre será ejecutado.
Mirando esto desde la perspectiva de la gestión de recursos:
- Declaramos el "recurso" (es decir, la variable del
reader
) antes del bloquetry
para que esté dentro del alcance del bloquefinally
. - Al colocar el
new FileReader(...)
, lacatch
puede manejar cualquier excepciónIOError
produce al abrir el archivo. - Necesitamos un
reader.close()
en el bloquefinally
porque hay algunas rutas de excepción que no podemos interceptar ni en el bloquetry
ni en el bloquecatch
. - Sin embargo, dado que se podría haber lanzado una excepción antes de que se inicializara el
reader
, también necesitamos una pruebanull
explícita. - Finalmente, la llamada
reader.close()
podría (hipotéticamente) lanzar una excepción. No nos importa eso, pero si no detectamos la excepción en la fuente, tendremos que lidiar con eso más arriba en la pila de llamadas.
Java 7 y versiones posteriores ofrecen una sintaxis alternativa de prueba con recursos que simplifica significativamente la limpieza de recursos.
La cláusula de 'tiros' en una declaración de método.
El mecanismo de excepción comprobada de Java requiere que el programador declare que ciertos métodos podrían generar excepciones comprobadas específicas. Esto se hace utilizando la cláusula de throws
. Por ejemplo:
public class OddNumberException extends Exception { // a checked exception
}
public void checkEven(int number) throws OddNumberException {
if (number % 2 != 0) {
throw new OddNumberException();
}
}
La throws OddNumberException
declara que una llamada a checkEven
podría lanzar una excepción de tipo OddNumberException
.
Una cláusula de throws
puede declarar una lista de tipos y puede incluir excepciones no comprobadas, así como excepciones comprobadas.
public void checkEven(Double number)
throws OddNumberException, ArithmeticException {
if (!Double.isFinite(number)) {
throw new ArithmeticException("INF or NaN");
} else if (number % 2 != 0) {
throw new OddNumberException();
}
}
¿Cuál es el punto de declarar excepciones no marcadas como lanzadas?
La cláusula de throws
en una declaración de método tiene dos propósitos:
Le dice al compilador qué excepciones se lanzan para que el compilador pueda reportar las excepciones sin marcar (marcadas) como errores.
Le dice a un programador que está escribiendo un código que llama al método qué excepciones esperar. Para este propósito, a menudo hace que los sentidos incluyan excepciones no verificadas en una lista de
throws
.
Nota: la herramienta javadoc también utiliza la lista de throws
al generar la documentación de la API y las sugerencias de métodos de "texto flotante" de un IDE típico.
Tiros y anulación de método.
La cláusula de los throws
forma parte de la firma de un método con el propósito de anular el método. Un método de anulación se puede declarar con el mismo conjunto de excepciones marcadas que el método anulado, o con un subconjunto. Sin embargo, el método de anulación no puede agregar excepciones marcadas adicionales. Por ejemplo:
@Override
public void checkEven(int number) throws NullPointerException // OK—NullPointerException is an unchecked exception
...
@Override
public void checkEven(Double number) throws OddNumberException // OK—identical to the superclass
...
class PrimeNumberException extends OddNumberException {}
class NonEvenNumberException extends OddNumberException {}
@Override
public void checkEven(int number) throws PrimeNumberException, NonEvenNumberException // OK—these are both subclasses
@Override
public void checkEven(Double number) throws IOExcepion // ERROR
La razón de esta regla es que si un método anulado puede lanzar una excepción marcada que el método anulado no podría lanzar, eso interrumpiría la sustituibilidad del tipo.