Domanda Una caratteristica peculiare dell'inferenza di tipo eccezione in Java 8


Mentre scrivevo il codice per un'altra risposta su questo sito mi sono imbattuto in questa peculiarità:

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;
}

In primo luogo, sono abbastanza confuso perché il sneakyThrow la chiamata è OK per il compilatore. Che tipo possibile ha inferto per T quando non viene menzionata alcuna parte di un tipo di eccezione non controllata?

In secondo luogo, accettando che funzioni, perché allora il compilatore si lamenta del nonSneakyThrow chiamata? Sembrano molto simili.


79
2017-07-09 11:49


origine


risposte:


Il T di sneakyThrow si suppone che sia RuntimeException. Questo può essere seguito dalle specifiche di langauge sull'inferenza di tipo (http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html)

Innanzitutto, c'è una nota nella sezione 18.1.3:

Un limite del modulo throws α è puramente informativo: indirizza la risoluzione per ottimizzare l'istanziazione di α in modo che, se possibile, non sia un tipo di eccezione controllata.

Ciò non influisce su nulla, ma ci indirizza alla sezione Risoluzione (18.4), che ha ottenuto più informazioni sui tipi di eccezione dedotti con un caso speciale:

... Altrimenti, se il set associato contiene throws αie i limiti superiori appropriati di αi sono, al massimo, Exception, Throwable, e Object, quindi Ti = RuntimeException.

Questo caso si applica a sneakyThrow - l'unico limite superiore è Throwable, così T si suppone che sia RuntimeException come da specifiche, quindi compila. Il corpo del metodo è irrilevante: il cast non controllato ha esito positivo in fase di runtime perché in realtà non accade, lasciando un metodo in grado di sconfiggere il sistema di eccezioni verificato in fase di compilazione.

nonSneakyThrow non viene compilato come quel metodo T ha un limite inferiore di Exception (vale a dire T deve essere un supertipo di Exception, o Exception stesso), che è un'eccezione controllata, a causa del tipo con cui viene chiamato, in modo che T viene inferito come Exception.


60
2017-07-09 12:01



Se l'inferenza di tipo produce un singolo limite superiore per una variabile di tipo, tipicamente il limite superiore viene scelto come soluzione. Ad esempio, se T<<Numberla soluzione è T=Number. Sebbene Integer, Float ecc. potrebbe anche soddisfare il vincolo, non ci sono buoni motivi per sceglierli Number.

Questo è stato anche il caso throws T in java 5-7: T<<Throwable => T=Throwable. (Le soluzioni di lancio subdole erano tutte esplicite <RuntimeException> digita argomenti, altrimenti <Throwable> è dedotto.)

In java8, con l'introduzione di lambda, questo diventa problematico. Considera questo 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 invochiamo con una lambda vuota, cosa succederebbe T essere dedotto come?

    invoke( ()->{} ); 

L'unico vincolo su T è un limite superiore Throwable. Nella prima fase di java8, T=Throwable sarebbe dedotto. Guarda questo rapporto Ho archiviato.

Ma è abbastanza sciocco, dedurre Throwable, un'eccezione controllata, da un blocco vuoto. Una soluzione è stata proposta nel rapporto (che è apparentemente adottato da 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)

cioè se il limite superiore è Exception o Throwable, scegli RuntimeException come soluzione. In questo caso, lì è una buona ragione per scegliere un sottotipo particolare del limite superiore.


16
2017-07-09 20:18



Con sneakyThrow, Il tipo T è una variabile di tipo generico limitata senza un tipo specifico (perché non esiste da dove il tipo potrebbe provenire).

Con nonSneakyThrow, Il tipo T è lo stesso tipo dell'argomento, quindi nel tuo esempio, il T di nonSneakyThrow(e); è Exception. Come testSneaky() non dichiara un lancio Exception, viene mostrato un errore.

Si noti che si tratta di un'interferenza nota di Generics con eccezioni controllate.


1
2017-07-09 11:54