Excepciones en Java, parte 2: características y tipos avanzados

JDK 1.0 introdujo un marco de características de lenguaje y tipos de biblioteca para lidiar con excepciones , que son divergencias del comportamiento esperado del programa. La primera mitad de este tutorial cubrió las capacidades básicas de manejo de excepciones de Java. Esta segunda mitad presenta capacidades más avanzadas proporcionadas por JDK 1.0 y sus sucesores: JDK 1.4, JDK 7 y JDK 9. Aprenda a anticipar y administrar excepciones en sus programas Java utilizando funciones avanzadas como seguimientos de pila, causas y encadenamiento de excepciones, pruebe -con-recursos, captura múltiple, relanzamiento final y caminata en pila.

Tenga en cuenta que los ejemplos de código de este tutorial son compatibles con JDK 12.

descargar Obtener el código Descargar el código fuente, por ejemplo, las aplicaciones de este tutorial. Creado por Jeff Friesen para JavaWorld.

Manejo de excepciones en JDK 1.0 y 1.4: seguimientos de pila

Cada subproceso de JVM (una ruta de ejecución) está asociado con una pila que se crea cuando se crea el subproceso. Esta estructura de datos se divide en marcos , que son estructuras de datos asociadas con llamadas a métodos. Por esta razón, la pila de cada hilo a menudo se denomina pila de llamadas a métodos .

Se crea un nuevo marco cada vez que se llama a un método. Cada marco almacena variables locales, variables de parámetros (que contienen argumentos pasados ​​al método), información para regresar al método de llamada, espacio para almacenar un valor de retorno, información que es útil para enviar una excepción, etc.

Un seguimiento de pila (también conocido como seguimiento de pila ) es un informe de los marcos de pila activos en un momento determinado durante la ejecución de un hilo. La Throwableclase de Java (en el java.langpaquete) proporciona métodos para imprimir un seguimiento de pila, completar un seguimiento de pila y acceder a los elementos de un seguimiento de pila.

Imprimir un seguimiento de pila

Cuando la throwinstrucción arroja un arrojar, primero busca un catchbloque adecuado en el método de ejecución. Si no se encuentra, desenrolla la pila de llamadas al método buscando el catchbloque más cercano que pueda manejar la excepción. Si no se encuentra, la JVM termina con un mensaje adecuado. Considere el Listado 1.

Listado 1. PrintStackTraceDemo.java(versión 1)

import java.io.IOException; public class PrintStackTraceDemo { public static void main(String[] args) throws IOException { throw new IOException(); } }

El ejemplo artificial del Listado 1 crea un java.io.IOExceptionobjeto y lo arroja fuera del main()método. Debido a main()que no maneja este lanzamiento y debido a que main()es el método de nivel superior, la JVM termina con un mensaje adecuado. Para esta aplicación, verá el siguiente mensaje:

Exception in thread "main" java.io.IOException at PrintStackTraceDemo.main(PrintStackTraceDemo.java:7)

La JVM genera este mensaje llamando Throwableal void printStackTrace()método 's , que imprime un seguimiento de la pila para el Throwableobjeto que invoca en el flujo de error estándar. La primera línea muestra el resultado de invocar el toString()método arrojable . La siguiente línea muestra los datos registrados previamente por fillInStackTrace()(discutidos en breve).

Métodos adicionales de seguimiento de la pila de impresión

ThrowableLos métodos void printStackTrace(PrintStream ps)y sobrecargados void printStackTrace(PrintWriter pw)generan el seguimiento de la pila en el flujo o escritor especificado.

El seguimiento de la pila revela el archivo de origen y el número de línea donde se creó el elemento arrojable. En este caso, se creó en la línea 7 del PrintStackTrace.javaarchivo fuente.

Puede invocar printStackTrace()directamente, normalmente desde un catchbloque. Por ejemplo, considere una segunda versión de la PrintStackTraceDemoaplicación.

Listado 2. PrintStackTraceDemo.java(versión 2)

import java.io.IOException; public class PrintStackTraceDemo { public static void main(String[] args) throws IOException { try { a(); } catch (IOException ioe) { ioe.printStackTrace(); } } static void a() throws IOException { b(); } static void b() throws IOException { throw new IOException(); } }

El Listado 2 revela un main()método que llama al método a(), que llama al método b(). El método b()lanza un IOExceptionobjeto a la JVM, que desenrolla la pila de llamadas al método hasta que encuentra main()el catchbloque, que puede manejar la excepción. La excepción se maneja invocando printStackTrace()el elemento arrojable. Este método genera la siguiente salida:

java.io.IOException at PrintStackTraceDemo.b(PrintStackTraceDemo.java:24) at PrintStackTraceDemo.a(PrintStackTraceDemo.java:19) at PrintStackTraceDemo.main(PrintStackTraceDemo.java:9)

printStackTrace()no muestra el nombre del hilo. En su lugar, invoca toString()en el elemento arrojable para devolver el nombre de clase completamente calificado del elemento arrojable ( java.io.IOException), que se muestra en la primera línea. Luego genera la jerarquía de llamadas al método: el método llamado más recientemente ( b()) está en la parte superior y main()en la parte inferior.

¿Qué línea identifica la traza de la pila?

El seguimiento de la pila identifica la línea donde se crea un elemento arrojadizo. No identifica la línea donde se lanza el lanzador (vía throw), a menos que el lanzador sea lanzado en la misma línea donde se creó.

Llenar un seguimiento de pila

Throwabledeclara un Throwable fillInStackTrace()método que completa el seguimiento de la pila de ejecución. En el Throwableobjeto que invoca , registra información sobre el estado actual de los marcos de pila del hilo actual. Considere el Listado 3.

Listado 3. FillInStackTraceDemo.java(versión 1)

import java.io.IOException; public class FillInStackTraceDemo { public static void main(String[] args) throws IOException { try { a(); } catch (IOException ioe) { ioe.printStackTrace(); System.out.println(); throw (IOException) ioe.fillInStackTrace(); } } static void a() throws IOException { b(); } static void b() throws IOException { throw new IOException(); } }

La principal diferencia entre el Listado 3 y el Listado 2 es la declaración catchdel bloque throw (IOException) ioe.fillInStackTrace();. Esta declaración reemplaza ioeel seguimiento de la pila, después de lo cual se vuelve a lanzar el elemento arrojadizo. Debería observar esta salida:

java.io.IOException at FillInStackTraceDemo.b(FillInStackTraceDemo.java:26) at FillInStackTraceDemo.a(FillInStackTraceDemo.java:21) at FillInStackTraceDemo.main(FillInStackTraceDemo.java:9) Exception in thread "main" java.io.IOException at FillInStackTraceDemo.main(FillInStackTraceDemo.java:15)

En lugar de repetir el seguimiento de pila inicial, que identifica la ubicación donde IOExceptionse creó el objeto, el segundo seguimiento de pila revela la ubicación de ioe.fillInStackTrace().

Constructores arrojables y fillInStackTrace()

Cada uno de Throwablelos constructores invoca fillInStackTrace(). Sin embargo, el siguiente constructor (introducido en JDK 7) no invocará este método cuando pase falsea writableStackTrace:

Throwable(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)

fillInStackTrace()invoca un método nativo que recorre la pila de llamadas al método del hilo actual para construir el seguimiento de la pila. Esta caminata es costosa y puede afectar el rendimiento si se realiza con demasiada frecuencia.

Si se encuentra con una situación (tal vez que involucre un dispositivo integrado) donde el rendimiento es crítico, puede evitar que se cree el seguimiento de la pila anulando fillInStackTrace(). Consulte el Listado 4.

Listado 4. FillInStackTraceDemo.java(versión 2)

{ public static void main(String[] args) throws NoStackTraceException { try { a(); } catch (NoStackTraceException nste) { nste.printStackTrace(); } } static void a() throws NoStackTraceException { b(); } static void b() throws NoStackTraceException { throw new NoStackTraceException(); } } class NoStackTraceException extends Exception { @Override public synchronized Throwable fillInStackTrace() { return this; } }

Listing 4 introduces NoStackTraceException. This custom checked exception class overrides fillInStackTrace() to return this -- a reference to the invoking Throwable. This program generates the following output:

NoStackTraceException

Comment out the overriding fillInStackTrace() method and you'll observe the following output:

NoStackTraceException at FillInStackTraceDemo.b(FillInStackTraceDemo.java:22) at FillInStackTraceDemo.a(FillInStackTraceDemo.java:17) at FillInStackTraceDemo.main(FillInStackTraceDemo.java:7)

Accessing a stack trace's elements

At times you'll need to access a stack trace's elements in order to extract details required for logging, identifying the source of a resource leak, and other purposes. The printStackTrace() and fillInStackTrace() methods don't support this task, but JDK 1.4 introduced java.lang.StackTraceElement and its methods for this purpose.

The java.lang.StackTraceElement class describes an element representing a stack frame in a stack trace. Its methods can be used to return the fully-qualified name of the class containing the execution point represented by this stack trace element along with other useful information. Here are the main methods:

  • String getClassName() returns the fully-qualified name of the class containing the execution point represented by this stack trace element.
  • String getFileName() returns the name of the source file containing the execution point represented by this stack trace element.
  • int getLineNumber() returns the line number of the source line containing the execution point represented by this stack trace element.
  • String getMethodName() returns the name of the method containing the execution point represented by this stack trace element.
  • boolean isNativeMethod() returns true when the method containing the execution point represented by this stack trace element is a native method.

JDK 1.4 also introduced the StackTraceElement[] getStackTrace() method to the java.lang.Thread and Throwable classes. This method respectively returns an array of stack trace elements representing the invoking thread's stack dump and provides programmatic access to the stack trace information printed by printStackTrace().

Listing 5 demonstrates StackTraceElement and getStackTrace().

Listing 5. StackTraceElementDemo.java (version 1)

import java.io.IOException; public class StackTraceElementDemo { public static void main(String[] args) throws IOException { try { a(); } catch (IOException ioe) { StackTraceElement[] stackTrace = ioe.getStackTrace(); for (int i = 0; i < stackTrace.length; i++) { System.err.println("Exception thrown from " + stackTrace[i].getMethodName() + " in class " + stackTrace[i].getClassName() + " on line " + stackTrace[i].getLineNumber() + " of file " + stackTrace[i].getFileName()); System.err.println(); } } } static void a() throws IOException { b(); } static void b() throws IOException { throw new IOException(); } }

When you run this application, you'll observe the following output:

Exception thrown from b in class StackTraceElementDemo on line 33 of file StackTraceElementDemo.java Exception thrown from a in class StackTraceElementDemo on line 28 of file StackTraceElementDemo.java Exception thrown from main in class StackTraceElementDemo on line 9 of file StackTraceElementDemo.java

Finalmente, JDK 1.4 introdujo el setStackTrace()método a Throwable. Este método está diseñado para su uso por marcos de llamadas a procedimiento remoto (RPC) y otros sistemas avanzados, lo que permite al cliente anular el seguimiento de pila predeterminado que se genera fillInStackTrace()cuando se construye un elemento arrojable.

Anteriormente mostré cómo anular fillInStackTrace()para evitar que se construya un seguimiento de pila. En su lugar, puede instalar un nuevo seguimiento de pila utilizando StackTraceElementy setStackTrace(). Cree una matriz de StackTraceElementobjetos inicializados mediante el siguiente constructor y pase esta matriz a setStackTrace():

StackTraceElement(String declaringClass, String methodName, String fileName, int lineNumber)

El Listado 6 demuestra StackTraceElementy setStackTrace().