todas - throws exception java
¿Qué partes de JLS justifican el hecho de poder lanzar excepciones marcadas como si no estuvieran seleccionadas? (5)
Bueno, esa es una de las múltiples formas de generar una excepción de verificación como si no estuviera marcada. Class.newInstance()
es otro, Thread.stop(Trowable)
uno en desuso.
La única forma de que JLS no acepte este comportamiento es si el tiempo de ejecución (JVM) lo aplicará.
En cuanto a donde se especifica: no lo es. Las excepciones marcadas y no marcadas se comportan igual. Las excepciones verificadas simplemente requieren un bloque catch o una cláusula de lanzamiento.
Edición : Basado en la discusión en los comentarios, un ejemplo basado en listas que expone la causa raíz: Borrado
public class Main {
public static void main(String[] args) {
List<Exception> myCheckedExceptions = new ArrayList<Exception>();
myCheckedExceptions.add(new IOException());
// here we''re tricking the compiler
@SuppressWarnings("unchecked")
List<RuntimeException> myUncheckedExceptions = (List<RuntimeException>) (Object) myCheckedExceptions;
// here we aren''t any more, at least not beyond the fact that type arguments are erased
throw throwAny(myUncheckedExceptions);
}
public static <T extends Throwable> T throwAny(Collection<T> throwables) throws T {
// here the compiler will insert an implicit cast to T (just like your explicit one)
// however, since T is a type variable, it gets erased to the erasure of its bound
// and that happens to be Throwable, so a useless cast...
throw throwables.iterator().next();
}
}
Recientemente descubrí y escribí en el blog sobre el hecho de que es posible colar una excepción comprobada a través del compilador javac y lanzarla a un lugar donde no se debe tirar. Esto se compila y ejecuta en Java 6 y 7, lanzando una SQLException
sin throws
o cláusula catch
:
public class Test {
// No throws clause here
public static void main(String[] args) {
doThrow(new SQLException());
}
static void doThrow(Exception e) {
Test.<RuntimeException> doThrow0(e);
}
static <E extends Exception> void doThrow0(Exception e) throws E {
throw (E) e;
}
}
El bytecode generado indica que a la JVM no le importan las excepciones marcadas / no marcadas:
// Method descriptor #22 (Ljava/lang/Exception;)V
// Stack: 1, Locals: 1
static void doThrow(java.lang.Exception e);
0 aload_0 [e]
1 invokestatic Test.doThrow0(java.lang.Exception) : void [25]
4 return
Line numbers:
[pc: 0, line: 11]
[pc: 4, line: 12]
Local variable table:
[pc: 0, pc: 5] local: e index: 0 type: java.lang.Exception
// Method descriptor #22 (Ljava/lang/Exception;)V
// Signature: <E:Ljava/lang/Exception;>(Ljava/lang/Exception;)V^TE;
// Stack: 1, Locals: 1
static void doThrow0(java.lang.Exception e) throws java.lang.Exception;
0 aload_0 [e]
1 athrow
Line numbers:
[pc: 0, line: 16]
Local variable table:
[pc: 0, pc: 2] local: e index: 0 type: java.lang.Exception
La JVM aceptando esto es una cosa. Pero tengo algunas dudas sobre si Java-the-language debería. ¿Qué partes del JLS justifican este comportamiento? ¿Es un error? ¿O una "característica" bien oculta del lenguaje Java?
Mis sentimientos son:
- La
<E>
dedoThrow()
está vinculada aRuntimeException
endoThrow()
. Por lo tanto, endoThrow()
no se necesita una cláusula dethrows
largo de las líneas de JLS §11.2 . -
RuntimeException
Exception
RuntimeException
es compatible con la asignación deException
, por lo tanto, el compilador no genera ninguna conversión (lo que daría como resultado unaClassCastException
).
Este ejemplo está documentado en el CERT Oracle Secure Coding Standard para Java, que documenta varios ejemplos de código no conformes.
Ejemplo de código no compatible (excepción genérica)
Una conversión no verificada de un tipo genérico con una declaración de excepción parametrizada también puede dar como resultado excepciones comprobadas inesperadas. Todos estos modelos son diagnosticados por el compilador a menos que se supriman las advertencias.
interface Thr<EXC extends Exception> {
void fn() throws EXC;
}
public class UndeclaredGen {
static void undeclaredThrow() throws RuntimeException {
@SuppressWarnings("unchecked") // Suppresses warnings
Thr<RuntimeException> thr = (Thr<RuntimeException>)(Thr)
new Thr<IOException>() {
public void fn() throws IOException {
throw new IOException();
}
};
thr.fn();
}
public static void main(String[] args) {
undeclaredThrow();
}
}
Esto funciona porque RuntimeException
es una clase secundaria de Exception
y no puede convertir ninguna clase que se extienda de Exception
a RuntimeException
pero si realiza la conversión como la siguiente, funcionará
Exception e = new IOException();
throw (RuntimeException) (e);
El caso que estás haciendo es el mismo que este. Debido a que este es un tipo explícito de conversión, esta llamada dará como resultado la ClassCastException
sin embargo el compilador lo permite.
Sin embargo, debido a Erasure, en su caso no hay un elenco involucrado y, por lo tanto, en su escenario no se produce la ClassCastException
En este caso, no hay forma de que el compilador restrinja la conversión de tipo que está haciendo.
Sin embargo, si cambia la firma del método a continuación, comenzará a quejarse.
static <E extends Exception> void doThrow0(E e) throws E {
JLS 11.2:
Para cada excepción marcada que es un posible resultado, la cláusula de lanzamientos para el método (§8.4.6) o el constructor (§8.8.5) debe mencionar la clase de esa excepción o una de las superclases de la clase de esa excepción (§ 11.2.3).
Esto indica claramente que DoThrow debe tener Excepción en su cláusula de lanzamientos. O, como está implicada la reducción (Exception to RuntimeException), se debe verificar que Exception IS RuntimeException, que debería fallar en el ejemplo porque la excepción que se está emitiendo es SQLException. Por lo tanto, ClassCastException debe lanzarse en tiempo de ejecución.
Desde el punto de vista práctico, este error de Java permite crear un hack inseguro para cualquier código de manejo de excepciones estándar, como el siguiente:
try {
doCall();
} catch (RuntimeException e) {
handle();
}
La excepción subirá sin ser manejada.
No creo que se pueda disputar que JLS permite ese código. La pregunta no es si ese código debe ser legal. El compilador simplemente no tiene suficiente información para emitir un error.
El problema aquí con el código emitido cuando se llama un método de lanzamiento genérico. El compilador actualmente inserta tipos de conversión después de llamar a un método genérico de retorno donde el valor devuelto se asignará inmediatamente a una referencia.
Si el compilador quisiera, podría evitar el problema rodeando silenciosamente todas las invocaciones de métodos de lanzamiento genérico en un fragmento try-catch-rethrow. Dado que la excepción se capturaría y asignaría a una variable local, un tipo de conversión sería obligatorio. Por lo tanto, obtendría una ClassCastException
si no fuera una instancia de RuntimeException
.
Pero la especificación no exige nada especial sobre los métodos de lanzamiento de genéricos. Creo que es un error de especificación. ¿Por qué no presentar un error sobre eso, para que se pueda corregir o al menos documentar?
Por cierto, la excepción ClassCastException
suprimiría la excepción original, lo que puede resultar en errores difíciles de encontrar. Pero ese problema tiene una solución simple desde JDK 7.
Todo esto equivale a explotar la brecha que una conversión no verificada a un tipo genérico no es un error del compilador. Su código se hace explícitamente inseguro de tipo si contiene tal expresión. Y dado que la verificación de las excepciones marcadas es estrictamente un procedimiento de tiempo de compilación, nada se interrumpirá en el tiempo de ejecución.
La respuesta de los autores de los genéricos probablemente estaría en la línea de "Si está usando modelos sin verificar, es su problema".
Veo algo bastante positivo en su descubrimiento: una ruptura de la fortaleza de las excepciones comprobadas. Desafortunadamente, esto no puede convertir las API envenenadas con excepciones marcadas existentes en algo más agradable de usar.
Cómo esto puede ayudar
En un proyecto de aplicación en capas típico mío habrá una gran cantidad de repetición como esta:
try {
... business logic stuff ...
}
catch (RuntimeException e) { throw e; }
catch (Exception e) { throw new RuntimeException(e); }
¿Por qué lo hago? Simple: no hay excepciones de valor de negocio para atrapar; cualquier excepción es un síntoma de un error de tiempo de ejecución. La excepción debe propagarse por la pila de llamadas hacia la barrera de excepción global. Con la fina contribución de Lukas, ahora puedo escribir.
try {
... business logic stuff ...
} catch (Exception e) { throwUnchecked(e); }
Esto puede no parecer mucho, pero los beneficios se acumulan después de repetirlo 100 veces a lo largo del proyecto.
Renuncia
En mis proyectos hay una gran disciplina con respecto a las excepciones, por lo que este es un buen ajuste para ellos Este tipo de engaño no es algo que se adopte como un principio de codificación general . Envolver la excepción sigue siendo la única opción segura en muchos casos.