tipos salida propagacion programacion personalizadas mas excepciones entrada comunes java generics java-8 type-inference

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 y Object , 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.