salida - Una característica peculiar de la inferencia de tipos de excepción en Java 8
tipos de excepciones en java pdf (3)
Mientras escribía código para otra respuesta en este sitio, me encontré con 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 llamada
sneakyThrow
está bien para el compilador.
¿Qué tipo posible infirió para
T
cuando no se menciona en ninguna parte un tipo de excepción no verificado?
En segundo lugar, aceptando que esto funciona, ¿por qué entonces el compilador se queja en la llamada
nonSneakyThrow
?
Se parecen mucho.
Con
sneakyThrow
, el tipo
T
es una variable de tipo genérica limitada
sin
un tipo específico (porque no hay de dónde podría provenir el tipo).
Con
nonSneakyThrow
, el tipo
T
es el mismo tipo que el argumento, por lo tanto, en su ejemplo, la
T
de
nonSneakyThrow(e);
es la
Exception
Como
testSneaky()
no declara una
Exception
lanzada, se muestra un error.
Tenga en cuenta que esta es una interferencia conocida de genéricos con excepciones marcadas.
Se infiere que la T de
sneakyThrow
es
RuntimeException
.
Esto puede seguirse de la especificación de idioma en la inferencia de tipos (
http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html
)
En primer lugar, hay una nota en la sección 18.1.3:
Un límite de la forma
throws α
es puramente informativo: dirige la resolución para optimizar la creación de instancias de α para que, si es posible, no sea un tipo de excepción verificado.
Esto no afecta nada, pero nos señala a la sección Resolución (18.4), que tiene más información sobre los tipos de excepción inferidos con un caso especial:
... De lo contrario, si el conjunto enlazado contiene
throws αi
, y los límites superiores adecuados de αi son, como máximo,Exception
,Throwable
yObject
, entonces Ti =RuntimeException
.
Este caso se aplica a
sneakyThrow
: el único límite superior es
Throwable
, por lo que se infiere que
T
es
RuntimeException
según la especificación, por lo que se compila.
El cuerpo del método es irrelevante: el lanzamiento no verificado tiene éxito en tiempo de ejecución porque en realidad no sucede, dejando un método que puede vencer al sistema de excepción verificado en tiempo de compilación.
nonSneakyThrow
no se compila ya que
T
ese método tiene un límite inferior de
Exception
(es decir,
T
debe ser un supertipo de
Exception
, o
Exception
sí), que es una excepción marcada, debido al tipo con el que se llama, para que
T
se infiera como
Exception
.
Si la inferencia de tipo produce un único límite superior para una variable de tipo, normalmente se elige el límite superior como la solución.
Por ejemplo, si
T<<Number
, la solución es
T=Number
.
Aunque
Integer
,
Float
, etc. también podrían satisfacer la restricción, no hay una buena razón para elegirlos en lugar de
Number
.
Ese también fue el caso de los
throws T
en Java 5-7:
T<<Throwable => T=Throwable
.
(Todas las soluciones de lanzamiento furtivo tenían argumentos explícitos de tipo
<RuntimeException>
, de lo contrario se infiere
<Throwable>
).
En java8, con la introducción de lambda, esto se vuelve problemático. Considera 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 infiere
T
?
invoke( ()->{} );
La única restricción en
T
es un límite superior
Throwable
.
En la etapa anterior de java8,
T=Throwable
sería inferido.
Vea este
report
que presenté.
Pero eso es bastante tonto, para inferir
Throwable
, una excepción marcada, de un bloque vacío.
Se propuso una solución en el informe (que aparentemente fue adoptado 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
o
Throwable
, elija
RuntimeException
como la solución.
En este caso, hay una buena razón para elegir un subtipo particular del límite superior.