Por que é throw e
permitido, mas não throw a
, no seguinte caso?
void test() {
try {
System.out.println();
} catch (Error | RuntimeException e) {
var a = e;
//throw a; unreported exception Throwable; must be caught or declared to be thrown
throw e;
}
}
Tudo isso intuitivamente faz sentido, mas a partir do JLS 14.20
Uma cláusula multi-catch pode ser considerada uma sequência de cláusulas uni-catch. Ou seja, uma cláusula catch onde o tipo do parâmetro de exceção é denotado como uma união
D1|D2|...|Dn
é equivalente a uma sequência de n cláusulas catch onde os tipos dos parâmetros de exceção são tipos de classeD1, D2, ..., Dn
respectivamente. No bloco de cada uma das n cláusulas catch, o tipo declarado do parâmetro de exceção élub(D1, D2, ..., Dn)
.
Como lub(Error,RuntimeException)
is Throwable
, o código acima deve ser equivalente a:
try {
System.out.println();
} catch (Error e) {
Throwable lub = e;
throw lub;
} catch (RuntimeException e) {
Throwable lub = e;
throw lub;
}
(que obviamente não compila)
Além disso, o tipo de a
é o tipo de e
"quando tratado como se não aparecesse em um contexto de atribuição" ( JLS 14.4.1 ), mas como mostrado acima não é o mesmo que o tipo de e
.
Há algo que eu esqueci?
Editar : esta não é uma duplicata de Por que é legal lançar novamente um Throwable em certos casos, sem declará-lo? porque esta questão é específica para multi-catch (que não é discutida lá) e surge devido a um mal-entendido de um fragmento específico do JLS que aborda multi-catch. As respostas fornecidas nesta pergunta me ajudaram a ligar os pontos :)
Isso é descrito em 11.2.2 . Isso basicamente se resume a "parâmetros de exceção
catch (...)
são um caso especial".Nosso objetivo é mostrar
throw e;
compilações. Começamos com esta linha em 11.2.3:Então, queremos mostrar que a
try
instrução intest
não pode ser lançadaThrowable
. Observe que “pode lançar” neste contexto é um termo rigorosamente definido. De acordo com 11.2.2,Apenas o segundo ponto é relevante aqui. Vamos mostrar que o
catch
bloco não pode ser lançadoThrowable
. Ou seja,throw e;
não pode lançarThrowable
.throw e;
aqui falha imediatamente o primeiro ponto, então podemos concluir quethrow e;
não é possível lançarThrowable
.Se fosse
throw a;
, então o acima não se aplica, mas outra cláusula se aplica:O compilador Java é inteligente o suficiente para reconhecer que capturar
e
e lançare
significará lançarRuntimeException
ouError
e, portanto, não uma exceção verificada que não foi declarada.Por outro lado, o tipo de
var a
é inferidoThrowable
(e nãoError | RuntimeException
como você pode pensar, já que tal declaração não é realmente válida nesse contexto!) e, portanto,throw a
significa que você lança umThrowable
e, portanto, o compilador não sabe com certeza você não está lançando uma exceção verificada e, como tal, gera o erro.O compilador poderia ser alterado para reconhecer isso? Provavelmente sim. Faria sentido fazer isso? Provavelmente não (ou pelo menos o valor de fazer provavelmente não vale o investimento).
Isto segue as regras especificadas em 14.18 A
throw
Declaração combinada com 11.2.2 Análise de Exceções de Declarações .Segue especificamente desta regra em 11.2.2 para
throw e
:Por outro lado, para
throw a
, aplica-se o seguinte:A parte saliente do JLS é 11.2.2 que diz:
Isso explica por que o seguinte irá lançar um
Error
ouRuntimeException
:Mas no seguinte:
o tipo inferido
a
éThrowable
(o limite superior). E isso resultará em um erro de compilação... se o método envolvente não for declarado como throwThrowable
.Para a
var
declaração, JLS 14.4.1 diz o seguinte:Isso é difícil de analisar, mas meu entendimento é que "a projeção ascendente de T" é o mesmo tipo conhecido como "limite superior mínimo". No
test2
exemplo seriaThrowable
.Observe que o texto JLS que você citou em sua pergunta é um texto ilustrativo e não normativo. E não descreve o que acontece quando você (re) lança a exceção capturada.