web-dev-qa-db-fra.com

Une caractéristique particulière de l'inférence de type d'exception dans Java 8

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.

82
Marko Topolnik

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

63
thecoop

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.

16
ZhongYu

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.

1
llogiq