Una característica peculiar de la inferencia de tipo de excepción en Java 8

Al escribir el 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  void sneakyThrow(Throwable t) throws T { throw (T) t; } static  void nonSneakyThrow(T t) throws T { throw t; } 

En primer lugar, estoy bastante confundido sobre por qué la llamada sneakyThrow está bien para el comstackdor. ¿Qué tipo posible infería para 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 comstackdor se queja en la llamada que no es nonSneakyThrow ? Se parecen mucho.

La T de sneakyThrow se infiere como RuntimeException . Esto se puede seguir desde la especificación de idioma en la inferencia de tipo ( 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 los throws α forma throws α es puramente informativo: dirige la resolución para optimizar la instanciación de α de modo que, si es posible, no es un tipo de excepción comprobada.

Esto no afecta nada, pero nos dirige a la sección Resolución (18.4), que tiene más información sobre los tipos de excepciones inferidas con un caso especial:

… De lo contrario, si el conjunto atado contiene throws αi , y los límites superiores propios de αi son, como máximo, Exception , Throwable , y Object , entonces Ti = RuntimeException .

Este caso se aplica a sneakyThrow : el único límite superior es Throwable , por lo que se deduce que T es RuntimeException según la especificación, por lo que se comstack. El cuerpo del método es inmaterial: el reparto no seleccionado tiene éxito en el tiempo de ejecución porque en realidad no sucede, dejando un método que puede vencer al sistema de excepción comprobado en tiempo de comstackción.

nonSneakyThrow no comstack como T ese método tiene un límite inferior de Exception (es decir, T debe ser un supertipo de Exception , o Exception misma), que es una excepción comprobada, debido al tipo con el que se está convocando, por lo que T se deduce como Exception .

Si la inferencia de tipo produce un límite superior único para una variable de tipo, normalmente se elige el límite superior como la solución. Por ejemplo, si T< , 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 sobre Number .

Ese fue también el caso para los throws T en java 5-7: T< T=Throwable . (Las soluciones Sneaky throw tenían argumentos explícitos de tipo ; de lo contrario, se deduce ).

En java8, con la introducción de lambda, esto se vuelve problemático. Considera este caso

 interface Action { void doIt() throws T; }  void invoke(Action action) throws T { action.doIt(); // throws T } 

Si invocamos con una lambda vacía, ¿cómo se inferiría T ?

  invoke( ()->{} ); 

La única restricción en T es un límite superior Throwable . En una etapa anterior de java8, se deduciría T=Throwable Throwable. Ver este informe que archivé.

Pero eso es bastante tonto, deducir Throwable , una excepción comprobada, de un bloque vacío. Se propuso una solución en el informe (que aparentemente es 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<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.

Con sneakyThrow , el tipo T es una variable de tipo genérico limitada sin un tipo específico (porque no hay ningún lugar desde el que pueda venir el tipo).

Con nonSneakyThrow , el tipo T es del mismo tipo que el argumento, por lo tanto, en su ejemplo, el T de nonSneakyThrow(e); es una Exception Como testSneaky() no declara una Exception lanzada, se muestra un error.

Tenga en cuenta que esta es una interferencia conocida de Generics con excepciones marcadas.