Mientras escribía código para otra respuesta en este sitio, encontré esta peculiaridad:
static void testSneaky() {
final Exception e = new Exception();
sneakyThrow(e); //no problems here
nonSneakyThrow(e); //ERRROR: Unhandled exception: java.lang.Exception
}
@SuppressWarnings("unchecked")
static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
throw (T) t;
}
static <T extends Throwable> void nonSneakyThrow(T t) throws T {
throw t;
}
Primero, estoy bastante confundido por qué la sneakyThrow
llamada está bien para el compilador. ¿Qué tipo posible dedujo T
cuando no se menciona en ninguna parte un tipo de excepción sin marcar?
En segundo lugar, aceptando que esto funciona, ¿por qué entonces el compilador se queja en la nonSneakyThrow
llamada? Se parecen mucho.
fuente
sneakyThrow
llamada. Las regulaciones especiales sobre la inferencia dethrows T
formas no existían en la especificación de Java 7.nonSneakyThrow
,T
debe serException
, no "un supertipo de"Exception
, porque es exactamente el tipo de argumento declarado en tiempo de compilación en el sitio de llamada.Exception
y un límite superiorThrowable
, por lo que el límite superior mínimo, que es el tipo inferido resultante, esException
.Exception
frase "o sí mismo" puede ser útil para el lector, pero en general, debe tenerse en cuenta que la especificación siempre usa los términos "subtipo" y "supertipo" en el sentido de "incluirse a sí mismo" ...Si la inferencia de tipo produce un único límite superior para una variable de tipo, normalmente se elige el límite superior como solución. Por ejemplo, si
T<<Number
la solución esT=Number
. AunqueInteger
,Float
etc. también podrían satisfacer la restricción, no hay una buena razón para elegirlosNumber
.Ese fue también el caso para
throws T
en Java 5-7:T<<Throwable => T=Throwable
. (Todas las soluciones de lanzamiento furtivo tenían<RuntimeException>
argumentos de tipo explícitos , de lo contrario<Throwable>
se infiere).En java8, con la introducción de lambda, esto se vuelve problemático. Considere este caso
interface Action<T extends Throwable> { void doIt() throws T; } <T extends Throwable> void invoke(Action<T> action) throws T { action.doIt(); // throws T }
Si invocamos con una lambda vacía, ¿cómo se
T
inferiría?La única restricción
T
es un límite superiorThrowable
. En una etapa anterior de java8,T=Throwable
se inferiría . Vea este informe que presenté.Pero eso es bastante tonto, inferir
Throwable
, una excepción marcada, de un bloque vacío. En el informe se propuso una solución (que aparentemente fue adoptada por JLS):If E has not been inferred from previous steps, and E is in the throw clause, and E has an upper constraint E<<X, if X:>RuntimeException, infer E=RuntimeException otherwise, infer E=X. (X is an Error or a checked exception)
es decir, si el límite superior es
Exception
oThrowable
, elijaRuntimeException
como solución. En este caso, no es una buena razón para elegir un subtipo particular de la cota superior.fuente
X:>RuntimeException
en su último fragmento de ejemplo?Con
sneakyThrow
, el tipoT
es una variable de tipo genérico acotado sin un tipo específico (porque no hay de dónde podría provenir el tipo).Con
nonSneakyThrow
, el tipoT
es del mismo tipo que el argumento, por lo que en su ejemplo,T
denonSneakyThrow(e);
esException
. ComotestSneaky()
no declara un arrojadoException
, se muestra un error.Tenga en cuenta que se trata de una interferencia conocida de los genéricos con las excepciones marcadas.
fuente
sneakyThrow
, ¿no se infiere realmente de ningún tipo específico, y el "elenco" es de un tipo tan indefinido? Me pregunto qué pasa realmente con esto.