En écrivant du code pour une autre réponse sur ce site, je suis tombé sur cette particularité:
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;
}
Tout d'abord, je ne comprends pas pourquoi l'appel sneakyThrow
est OK pour le compilateur. Quel type possible a-t-il déduit pour T
lorsqu'il n'y a aucune mention nulle part d'un type d'exception non contrôlé?
Deuxièmement, en acceptant que cela fonctionne, pourquoi alors le compilateur se plaint-il de l'appel nonSneakyThrow
? Ils se ressemblent beaucoup.
Le T de sneakyThrow
est supposé être RuntimeException
. Cela peut être suivi à partir de la spécification langauge sur l'inférence de type ( http://docs.Oracle.com/javase/specs/jls/se8/html/jls-18.html )
Tout d'abord, il y a une note dans la section 18.1.3:
Une limite de la forme
throws α
est purement informatif: il dirige la résolution pour optimiser l'instanciation de α afin que, si possible, ce ne soit pas un type d'exception vérifié.
Cela n'affecte rien, mais cela nous renvoie à la section Résolution (18.4), qui contient plus d'informations sur les types d'exceptions déduites avec un cas spécial:
... Sinon, si l'ensemble lié contient
throws αi
, et les limites supérieures appropriées de αi sont, tout au plus,Exception
,Throwable
etObject
, puis Ti =RuntimeException
.
Ce cas s'applique à sneakyThrow
- la seule limite supérieure est Throwable
, donc T
est supposé être RuntimeException
selon la spécification, donc il compile. Le corps de la méthode est sans importance - le transtypage non contrôlé réussit au moment de l'exécution car il ne se produit pas réellement, laissant une méthode qui peut vaincre le système d'exception vérifié au moment de la compilation.
nonSneakyThrow
ne se compile pas car T
de cette méthode a une limite inférieure de Exception
(ie T
doit être un sur-type de Exception
, ou Exception
lui-même), qui est une exception vérifiée, en raison du type avec lequel il est appelé, de sorte que T
est déduit comme Exception
.
Si l'inférence de type produit une limite supérieure unique pour une variable de type, généralement la limite supérieure est choisie comme solution. Par exemple, si T<<Number
, la solution est T=Number
. Bien que Integer
, Float
etc. puissent également satisfaire la contrainte, il n'y a aucune bonne raison de les choisir sur Number
.
Ce fut également le cas pour throws T
in Java 5-7: T<<Throwable => T=Throwable
. (Les solutions de lancer sournoises avaient toutes _ <RuntimeException>
arguments de type, sinon <Throwable>
est déduit.)
En Java8, avec l'introduction de lambda, cela devient problématique. Considérez ce cas
interface Action<T extends Throwable>
{
void doIt() throws T;
}
<T extends Throwable> void invoke(Action<T> action) throws T
{
action.doIt(); // throws T
}
Si nous invoquons avec un lambda vide, à quoi serait déduit T
?
invoke( ()->{} );
La seule contrainte sur T
est une limite supérieure Throwable
. À un stade antérieur de Java8, T=Throwable
serait déduit. Voir ceci rapport J'ai déposé.
Mais c'est assez idiot, pour déduire Throwable
, une exception vérifiée, d'un bloc vide. Une solution a été proposée dans le rapport (qui est apparemment adopté par 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)
par exemple, si la limite supérieure est Exception
ou Throwable
, choisissez RuntimeException
comme solution. Dans ce cas, il y a une bonne raison de choisir un sous-type particulier de la borne supérieure.
Avec sneakyThrow
, le type T
est une variable de type générique bornée sans un type spécifique (car il n'y a pas d'où le type pourrait provenir).
Avec nonSneakyThrow
, le type T
est le même type que l'argument, ainsi dans votre exemple, le T
de nonSneakyThrow(e);
est Exception
. Comme testSneaky()
ne déclare pas un Exception
levé, une erreur est affichée.
Notez qu'il s'agit d'une interférence connue des génériques avec des exceptions vérifiées.