how - Java SneakyThrow de excepciones, borrado de tipo
how to put title on jframe (4)
¿Alguien puede explicar este código?
public class SneakyThrow {
public static void sneakyThrow(Throwable ex) {
SneakyThrow.<RuntimeException>sneakyThrowInner(ex);
}
private static <T extends Throwable> T sneakyThrowInner(Throwable ex) throws T {
throw (T) ex;
}
public static void main(String[] args) {
SneakyThrow.sneakyThrow(new Exception());
}
}
Puede parecer extraño, pero esto no produce una excepción de conversión, y permite lanzar una excepción comprobada sin tener que declararla en la firma, o envolverla en una excepción no seleccionada.
Tenga en cuenta que ni sneakyThrow(...)
ni el main están declarando una excepción comprobada, pero la salida es:
Exception in thread "main" java.lang.Exception
at com.xxx.SneakyThrow.main(SneakyThrow.java:20)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Este truco se usa en Lombok, con la anotación @SneakyThrow, que permite lanzar excepciones comprobadas sin declararlas.
Sé que tiene algo que ver con el borrado de tipos, pero no estoy seguro de entender cada parte del hack.
Edición: Sé que podemos insertar un Integer
en una List<String>
y que la distinción de excepciones marcadas / no marcadas es una característica de tiempo de compilación.
Cuando se realiza la conversión de un tipo no genérico como List
a un tipo genérico como List<XXX>
el compilador produce una advertencia. Pero es menos común convertir a un tipo genérico directamente como (T) ex
en el código anterior.
Si lo desea, lo que me parece extraño es que entiendo que dentro de la JVM, la List<Dog>
y la List<Cat>
tiene el mismo aspecto, pero el código anterior parece indicar que finalmente también podemos asignar un valor de tipo Gato a una variable de tipo Perro o algo así.
El código anterior parece significar que finalmente también podemos asignar un valor de tipo Cat a una variable de tipo Dog o algo así.
Tienes que pensar en términos de cómo están estructuradas las clases. T extends Throwable
y le estás pasando una Exception
. Esto es como asignar Dog
a Animal
no Dog
a Cat
.
El compilador tiene reglas sobre qué Throwable se comprueba y cuáles no, según la herencia. Estos se aplican en tiempo de compilación y es posible confundir al compilador para que le permita lanzar una excepción de verificación. En tiempo de ejecución esto no tiene impacto.
Las excepciones comprobadas son una característica de tiempo de compilación (como los genéricos)
Por cierto, Throwable es una excepción marcada también. Si lo clasifica, se revisará a menos que sea una subclase de Error o RuntimeException.
Otra forma de lanzar excepciones comprobadas sin que el compilador se dé cuenta de que está haciendo esto.
Thread.currentThread().stop(throwable);
Unsafe.getUnsafe().throwException(throwable);
La única diferencia es que ambos usan código nativo.
A partir de Java 8, el método de ayuda sneakyThrowInner
ya no es necesario. sneakyThrow
se puede escribir como:
@SuppressWarnings("unchecked")
static <T extends Throwable> RuntimeException sneakyThrow(Throwable t) throws T {
throw (T)t;
}
Consulte la publicación " Una característica peculiar de la inferencia de tipo de excepción en Java 8 ".
La T de sneakyThrow se infiere como RuntimeException. Esto se puede seguir desde la especificación de lenguaje en la inferencia de tipo ( http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html )
Nota: la función sneakyThrow
está declarada para devolver RuntimeException
, de modo que se puede utilizar de la siguiente manera:
int example() {
if (a_problem_occurred) {
throw sneakyThrow(new IOException("An I/O exception occurred"));
// Without the throw here, we''d need a return statement!
} else {
return 42;
}
}
Al lanzar la sneakyThrow
RuntimeException
devuelta por sneakyThrow
, el compilador de Java sabe que esta ruta de ejecución termina. (Por supuesto, sneakyThrow
no regresa).
Si lo compilas con -Xlint
obtendrás una advertencia:
c:/Users/Jon/Test>javac -Xlint SneakyThrow.java
SneakyThrow.java:9: warning: [unchecked] unchecked cast
throw (T) ex;
^
required: T
found: Throwable
where T is a type-variable:
T extends Throwable declared in method <T>sneakyThrowInner(Throwable)
1 warning
Básicamente, se trata de decir que "este lanzamiento no se verifica en el momento de la ejecución" (debido al borrado de tipo), por lo que el compilador asume a regañadientes que está haciendo lo correcto, sabiendo que en realidad no se revisará.
Ahora es solo el compilador el que se preocupa por las excepciones marcadas y no verificadas, no es en absoluto parte de la JVM. Así que una vez que haya pasado el compilador, estará en casa libre.
Sin embargo, le recomiendo que evite hacer esto.
En muchos casos, hay una verificación "real" cuando se usan genéricos porque algo usa el tipo deseado, pero no siempre es así. Por ejemplo:
List<String> strings = new ArrayList<String>();
List raw = strings;
raw.add(new Object()); // Haha! I''ve put a non-String in a List<String>!
Object x = strings.get(0); // This doesn''t need a cast, so no exception...
Veamos el siguiente código:
public class SneakyThrow {
public static void sneakyThrow(Throwable ex) {
SneakyThrow.<RuntimeException>sneakyThrowInner(ex);
}
private static <T extends Throwable> T sneakyThrowInner(Throwable ex) throws T {
throw (T) ex;
}
public static void main(String[] args) {
SneakyThrow.sneakyThrow(new Exception());
}
}
Veamos por qué se está comportando así.
En primer lugar, cualquier conversión a un parámetro de tipo ignorado por el compilador. JVM lo lanzará directamente. Entonces, throw (T) ex;
No se comprobará el tipo de seguridad por el compilador. Pero, cuando el código de tiempo llega a la JVM, se produce el borrado de tipo. Por lo tanto, el código será como: [Nota: el código real será un código de byte. A continuación es solo para explicar qué hace el borrado de tipo.]
public class SneakyThrow {
public static void sneakyThrow(Throwable ex) {
SneakyThrow.sneakyThrowInner(ex); // Note : Throwable is checked exception but we don''t mention anything here for it
}
private static Throwable sneakyThrowInner(Throwable ex) throws Throwable {
throw (Throwable) ex;
}
public static void main(String[] args) {
SneakyThrow.sneakyThrow(new Exception());
}
}
Lo primero que hay que tener en cuenta es que todo funcionará sin problemas y no se emitirá una ClassCastException, ya que JVM es capaz de escribir cast en Throwable
fácilmente.
Lo segundo que se debe tener en cuenta es que el compilador siempre nos obligó a detectar o pasar la excepción comprobada recibida. JVM no realiza tales controles. Aquí, después de escribir, idealmente, SneakyThrow.sneakyThrowInner(ex);
debe ser forzado a manejar la excepción Throwable
pero recuerde que el código de byte de esta versión se llega a JVM. Por lo tanto, de alguna manera engañamos al compilador.
Hazlo así :
public class SneakyThrow {
public static void sneakyThrow(Throwable ex) {
SneakyThrow.<RuntimeException>sneakyThrowInner(ex);
}
private static <T extends Throwable> T sneakyThrowInner(Throwable ex) throws T {
throw (T) ex;
}
public static void main(String[] args) {
try {
SneakyThrow.sneakyThrow(new Exception());
}catch (Throwable ex){
System.out.println("Done Succesfully"); // Added to show everything runs fine
}
}
}
Salida: Hecho con éxito