Buscar..


Introducción

Los objetos del tipo 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 del try...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 .
    • Si no lo es, la excepción original continúa propagándose.

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

Java SE 7

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 clase IllegalArgumentException , 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 como throws IllegalArgumentException . Esto no fue estrictamente necesario, ya que IllegalArgumentException 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:

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

Java SE 7

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 y ServerSocket y sus subclases
  • Channel y sus subclases, y
  • Las interfaces JDBC Connection , Statement y ResultSet 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 y finally .
  • 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:

  1. 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();
    
  2. 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.

Java SE 1.4

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:

  1. La distinción entre excepciones marcadas y no marcadas se describe a continuación.
  2. La clase Throwable , Exception y RuntimeException debe tratar como abstract ; ver Pitfall - Lanzar Throwable, Exception, Error o RuntimeException .
  3. La JVM lanza las excepciones de Error en situaciones en las que sería inseguro o imprudente que una aplicación intentara recuperarse.
  4. No sería prudente declarar subtipos personalizados de Throwable . Las herramientas y bibliotecas de Java pueden asumir que Error y Exception son los únicos subtipos directos de Throwable , 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 de throws 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:

  1. Comienza a ejecutar el código en el bloque try .
  2. 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 de ArithmeticException ).
  3. Si se captura la excepción, se asigna a la variable e y se ejecuta el bloque catch .
  4. Si se completa el bloque try o catch (es decir, no se producen excepciones no detectadas durante la ejecución del código), continúe ejecutando el código debajo del bloque try-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

Java SE 1.4

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:

  1. La información disponible en un StackTraceElement es limitada. No hay más información disponible de la que se muestra en printStackTrace . (Los valores de las variables locales en el marco no están disponibles.)

  2. 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.

Java SE 1.4

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 SE 7

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 .
  • 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.

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 bloque try para que esté dentro del alcance del bloque finally .
  • Al colocar el new FileReader(...) , la catch puede manejar cualquier excepción IOError produce al abrir el archivo.
  • Necesitamos un reader.close() en el bloque finally porque hay algunas rutas de excepción que no podemos interceptar ni en el bloque try ni en el bloque catch .
  • Sin embargo, dado que se podría haber lanzado una excepción antes de que se inicializara el reader , también necesitamos una prueba null 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 SE 7

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:

  1. Le dice al compilador qué excepciones se lanzan para que el compilador pueda reportar las excepciones sin marcar (marcadas) como errores.

  2. 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.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow