Enquanto escrevia o código para outra resposta neste site, me deparei com esta peculiaridade:
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;
}
Em primeiro lugar, estou bastante confuso por que a sneakyThrow
chamada é OK para o compilador. Que tipo possível ele inferiu T
quando não há menção em qualquer lugar de um tipo de exceção não verificado?
Em segundo lugar, aceitando que isso funciona, por que então o compilador reclama na nonSneakyThrow
chamada? Eles parecem muito parecidos.
fonte
sneakyThrow
. Os regulamentos especiais sobre a inferência dethrows T
formulários não existiam na especificação do Java 7.nonSneakyThrow
,T
deve serException
, não "um supertipo de"Exception
, porque é exatamente o tipo do argumento declarado em tempo de compilação no site da chamada.Exception
e um limite superior deThrowable
, então o menor limite superior, que é o tipo inferido resultante, éException
.Exception
frase "ou ela mesma" pode ser útil para o leitor, mas geralmente, deve-se notar que a especificação sempre usa os termos "subtipo" e "supertipo" no sentido de "incluir a si mesma" ...Se a inferência de tipo produz um único limite superior para uma variável de tipo, normalmente o limite superior é escolhido como a solução. Por exemplo, se
T<<Number
, a solução éT=Number
. EmboraInteger
,Float
etc. também possam satisfazer a restrição, não há um bom motivo para escolhê-losNumber
.Esse também foi o caso para
throws T
em java 5-7:T<<Throwable => T=Throwable
. (Todas as soluções de lançamento sorrateiro tinham<RuntimeException>
argumentos de tipo explícitos , caso contrário,<Throwable>
é inferido.)Em java8, com a introdução de lambda, isso se torna 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 }
Se invocarmos com um lambda vazio, o que seria
T
inferido como?A única restrição
T
é um limite superiorThrowable
. No estágio anterior de java8,T=Throwable
seria inferido. Veja este relatório que fiz.Mas isso é muito bobo, inferir
Throwable
, uma exceção verificada, de um bloco vazio. Uma solução foi proposta no relatório (que é aparentemente adotada pela 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)
ou seja, se o limite superior for
Exception
ouThrowable
, escolhaRuntimeException
como a solução. Neste caso, não é uma boa razão para escolher um subtipo particular do limite superior.fonte
X:>RuntimeException
em seu último trecho de exemplo?Com
sneakyThrow
, o tipoT
é uma variável de tipo genérico limitada sem um tipo específico (porque não há de onde o tipo poderia vir).Com
nonSneakyThrow
, o tipoT
é o mesmo que o argumento, portanto, em seu exemplo, oT
denonSneakyThrow(e);
éException
. ComotestSneaky()
não declara um lançadoException
, é mostrado um erro.Observe que esta é uma interferência conhecida de Genéricos com exceções verificadas.
fonte
sneakyThrow
não é realmente inferido para nenhum tipo específico, e o "elenco" é para um tipo indefinido? Eu me pergunto o que realmente acontece com isso.