¿Por qué se permite la captura de excepciones verificadas para el código que no arroja excepciones?


En Java, los métodos que lanzan checked exceptions ( Exception o sus subtipos-IOException, InterrumpedException, etc.) deben declarar throws statement:

public abstract int read() throws IOException;

Los métodos que no declaran throws la instrucción no pueden lanzar excepciones verificadas.

public int read() { // does not compile
    throw new IOException();
}
// Error: unreported exception java.io.IOException; must be caught or declared to be thrown

Pero la captura de excepciones verificadas en métodos seguros sigue siendo legal en java:

public void safeMethod() { System.out.println("I'm safe"); }

public void test() { // method guarantees not to throw checked exceptions
    try {
        safeMethod();
    } catch (Exception e) { // catching checked exception java.lang.Exception
        throw e; // so I can throw... a checked Exception?
    }
}

En realidad, no. Es un poco gracioso: el compilador sabe que e no es una excepción marcada y permite repensarlo. Las cosas son incluso un poco ridículas, este código no compila:

public void test() { // guarantees not to throw checked exceptions
    try {
        safeMethod();
    } catch (Exception e) {        
        throw (Exception) e; // seriously?
    }
}
// Error: unreported exception java.lang.Exception; must be caught or declared to be thrown

El primer fragmento fue una motivación para una pregunta.

El compilador sabe que las excepciones marcadas no se pueden lanzar dentro de un método seguro, por lo que tal vez debería permitir capturar solo excepciones sin marcar.


Volviendo a la pregunta principal - ¿hay alguna razón para implementar excepciones de captura verificadas de esta manera? ¿Es solo un defecto en el diseño o me estoy perdiendo algunos factores importantes - tal vez incompatibilidades hacia atrás? ¿Qué podría salir mal si solo se permitiera que RuntimeException fuera atrapado en este escenario? Los ejemplos son muy apreciados.

Author: AdamSkywalker, 2016-02-03

3 answers

Citando la Especificación del Lenguaje Java , §11.2.3:

Es un error en tiempo de compilación si una cláusula catch puede capturar la clase de excepción comprobada E1 y no es el caso que el bloque try correspondiente a la cláusula catch pueda lanzar una clase de excepción comprobada que sea una subclase o superclase de E1, a menos que E1 sea una Excepción o una superclase de Excepción.

Supongo que esta regla se originó mucho antes de Java 7, donde no existían las capturas múltiples. Pues, si tienes un bloque try que podría lanzar una multitud de excepciones, la forma más fácil de atrapar todo sería atrapar una superclase común (en el peor de los casos, Exception, o Throwable si quieres atrapar Errors también).

Tenga en cuenta que puede no capturar un tipo de excepción que no está completamente relacionado con lo que realmente se lanza - en su ejemplo, capturar cualquier subclase de Throwable que no es un RuntimeException será un error:

try {
    System.out.println("hello");
} catch (IOException e) {  // compilation error
    e.printStackTrace();
}


Editar por OP: El principal parte de la respuesta es el hecho de que los ejemplos de preguntas solo funcionan para la clase Exception. Por lo general, no se permite la captura de excepciones verificadas en lugares aleatorios del código. Lo siento si confundí a alguien usando estos ejemplos.
 19
Author: Aasmund Eldhuset,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-16 17:37:16

Java 7 introdujo una comprobación de tipos de excepción más inclusiva.

Sin embargo, en Java SE 7, puede especificar los tipos de excepción FirstException y SecondException en la cláusula throws en la declaración del método rethrowException. El compilador Java SE 7 puede determinar que la excepción lanzada por la instrucción throw e debe haber venido del bloque try, y las únicas excepciones lanzadas por el bloque try pueden ser FirstException y SecondException.

Esto passage habla de un bloque try que lanza específicamente FirstException y SecondException; a pesar de que el bloque catch lanza Exception, el método solo necesita declarar que lanza FirstException y SecondException, no Exception:

public void rethrowException(String exceptionName)
 throws FirstException, SecondException {
   try {
     // ...
   }
   catch (Exception e) {
     throw e;
   }
 }

Esto significa que el compilador puede detectar que los únicos tipos de excepción posibles lanzados en test son Error s o RuntimeException s, ninguno de los cuales necesita ser capturado. Cuando throw e;, puede decir, incluso cuando el tipo estático es Exception, que no necesita ser declarado o re-atrapado.

Pero cuando lo lanzas a Exception, esto pasa por alto esa lógica. Ahora el compilador lo trata como un Exception ordinario que necesita ser capturado o declarado.

La razón principal para agregar esta lógica al compilador era permitir al programador especificar solo subtipos específicos en la cláusula throws al repensar un Exception general que captura esos subtipos específicos. Sin embargo, en este caso, le permite capturar un general Exception y no tener que declarar cualquier exception in a throws clause, because no specific types that can be thrown are checked exceptions.

 11
Author: rgettman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-03 17:58:17

El problema aquí es que las limitaciones de excepción marcadas/no marcadas afectan lo que su código está permitido para lanzar, no lo que está permitido para atrapar. Si bien todavía puedes atrapar cualquier tipo de Exception, los únicos que puedes lanzar de nuevo son los que no están controlados. (Esta es la razón por la que convertir su excepción sin marcar en una excepción marcada rompe su código.)

La captura de una excepción no controlada con Exception es válida, porque las excepciones no controladas (a. k. a. RuntimeException s) son una subclase de Excepción, y sigue las reglas de polimorfismo estándar; no convierte la excepción atrapada en un Exception, al igual que almacenar un String en un Object no convierte la String en un Object. Polimorfismo significa que una variable que puede contener un Object puede contener cualquier cosa derivada de Object (tales como String). Del mismo modo, como Exception es la superclase de todos los tipos de excepción, una variable de tipo Exception puede contener cualquier clase derivada de Exception, sin girar el objeto en y Exception. Considere esto:

import java.lang.*;
// ...
public String iReturnAString() { return "Consider this!"; }
// ...
Object o = iReturnAString();

A pesar de que el tipo de la variable es Object, o todavía almacena un String, ¿no es así? Del mismo modo, en su código:

try {
    safeMethod();
} catch (Exception e) { // catching checked exception
    throw e; // so I can throw... a checked Exception?
}

Lo que esto significa es en realidad "atrapar cualquier cosa compatible con la clase Exception (es decir, Exception y cualquier cosa derivada de ella)."Lógica similar se utiliza en otros lenguajes, también; por ejemplo, en C++, catching a std::exception también atrapará std::runtime_error, std::logic_error, std::bad_alloc, cualquier excepción creada por el usuario correctamente definida, y así sucesivamente, porque todos derivan de std::exception.

Tl; dr: No estás capturando excepciones marcadas, estás capturando cualquier excepciones. La excepción solo se convierte en una excepción marcada si la convierte en un tipo de excepción marcada.

 7
Author: Justin Time,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-03 18:23:57