2015-07-09 8 views
75

Durante la scrittura di codice per un'altra risposta su questo sito mi sono imbattuto in questa particolarità:Una caratteristica peculiare del tipo di eccezione l'inferenza in Java 8

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é la chiamata sneakyThrow è OK per il compilatore . Quale tipo possibile ha inferito per T quando non si fa menzione di alcun tipo di eccezione non controllata?

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

risposta

55

La T di sneakyThrow è considerata RuntimeException. Questo può essere seguito dal spec langauge del tipo inferenza (http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html)

primo luogo, c'è una nota nella sezione 18.1.3:

A associata del modulo throws α è puramente informativo: dirige risoluzione ottimizzare l'istanziazione di α in modo che, se possibile, non sia un tipo di eccezione controllata.

Ciò non pregiudica nulla, ma ci indica la sezione Risoluzione (18,4), che ha ottenuto maggiori informazioni sui tipi di eccezione suggeriti a un caso speciale:

... In caso contrario, se il set rilegato contiene throws αi e i limiti superiori corretti di αi sono, al massimo, Exception, Throwable e Object, quindi Ti = RuntimeException.

Questo caso si applica al sneakyThrow - l'unico limite superiore è Throwable, così T si inferisce essere RuntimeException secondo le specifiche, quindi si 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 compila come quella del metodo T ha un limite inferiore di Exception (cioè T deve essere un supertipo di Exception, o Exception stessa), che è un'eccezione controllata, a causa del tipo che viene chiamato, per cui che T viene dedotto come Exception.

+0

Perché la prima riga ha anche un errore in Java 7? – Maksym

+1

@Maksym Si deve intendere la chiamata 'sneakyThrow'. Le regole speciali sull'inferenza delle forme 'getta T' non esistevano nelle specifiche di Java 7. –

+0

Piccola nitpick: In' nonSneakyThrow', 'T' deve essere' Exception', non "un supertipo di" 'Exception', perché è esattamente il tipo di argomento dichiarato in fase di compilazione nel sito di chiamata. – llogiq

1

Con sneakyThrow, del tipo T è un tipo generico limitata variabile senza un tipo specifico (perché non c'è dove il tipo potrebbe venire da).

Con nonSneakyThrow, il tipo di T è dello stesso tipo come argomento, quindi nel tuo esempio, la T di nonSneakyThrow(e); è Exception. Poiché testSneaky() non dichiara un lancio Exception, viene visualizzato un errore.

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

+0

Quindi per 'sneakyThrow' non viene in realtà dedotto da alcun tipo specifico e il" cast "è per un tipo non definito? Mi chiedo che cosa accada realmente con questo. –

15

Se l'inferenza di tipo produce un singolo limite superiore per una variabile di tipo, in genere il limite superiore viene scelto come soluzione.Ad esempio, se T<<Number, la soluzione è T=Number. Sebbene Integer, Float ecc. Possano soddisfare anche il vincolo, non è un buon motivo per sceglierli su Number.

Questo era anche il caso per throws T in Java 5-7: T<<Throwable => T=Throwable. (Tutte le soluzioni di lancio subiscono espliciti argomenti di tipo <RuntimeException>, altrimenti viene dedotta <Throwable>.)

In java8, con l'introduzione di lambda, questo diventa problematico. Considerate 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 vuoto, quello che sarebbe T dedurre come?

invoke(()->{}); 

L'unico vincolo T è un limite superiore Throwable. Nella fase precedente di java8, sarebbe stato dedotto T=Throwable. Vedi questo report Ho archiviato.

Ma è piuttosto sciocco, dedurre Throwable, un'eccezione controllata, da un blocco vuoto. Una soluzione è stata proposta nella relazione (che a quanto pare è 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) 

vale a dire se il limite superiore è Exception o Throwable, scegliere RuntimeException come la soluzione. In questo caso, lo è un buon motivo per scegliere un sottotipo particolare del limite superiore.

Problemi correlati