2009-02-01 18 views
116

Secondo il Java Language Sepecification, 3a edizione:Perché Java non consente sottoclassi generiche di Throwable?

It is a compile-time error if a generic class is a direct or indirect subclass of Throwable.

Vorrei capire perché è stato preso questa decisione. Cosa c'è di sbagliato con le eccezioni generiche?

(Per quanto ne so, i generici sono semplicemente in fase di compilazione zucchero sintattico, e saranno tradotti in Object comunque nei file .class, così efficacemente dichiarando una classe generica è come se tutto ciò che è stato un Object. Si prega correggetemi se sbaglio)

+1

argomenti di tipo generico sono sostituiti dal limite superiore, che di default è oggetto. Se hai qualcosa come l'Elenco , allora A viene usato nei file di classe. –

+0

Grazie @Torsten. Non ho pensato a quel caso prima. –

+2

È una buona domanda per l'intervista, questa. – skaffman

risposta

128

Come marchio detto, i tipi non sono reifiable, che è un problema nel seguente caso:

try { 
    doSomeStuff(); 
} catch (SomeException<Integer> e) { 
    // ignore that 
} catch (SomeException<String> e) { 
    crashAndBurn() 
} 

Sia SomeException<Integer> e SomeException<String> vengono cancellati allo stesso tipo, non v'è alcun modo per il JVM distinguere le istanze di eccezione, e quindi nessun modo per dire quale blocco catch deve essere eseguito.

+1

ma cosa significa "reificabile"? – aberrant80

+1

@ aberrant80: semplicemente parlando significa che è possibile accedere al tipo di calcestruzzo in fase di esecuzione. Quale non è il fatto in Java. –

+34

Quindi la regola non dovrebbe essere "i tipi generici non possono sottoclasse Throwable", ma invece "le clausole catch devono sempre usare i tipi raw". – Archie

13

Ecco un semplice esempio di come utilizzare l'eccezione:.

class IntegerExceptionTest { 
    public static void main(String[] args) { 
    try { 
     throw new IntegerException(42); 
    } catch (IntegerException e) { 
     assert e.getValue() == 42; 
    } 
    } 
} 

il corpo della dichiarazione try genera l'eccezione con un dato valore, che viene catturato da la clausola di cattura.

Al contrario, è vietata la seguente definizione di una nuova eccezione, perché crea un tipo parametrico:

class ParametricException<T> extends Exception { // compile-time error 
    private final T value; 
    public ParametricException(T value) { this.value = value; } 
    public T getValue() { return value; } 
} 

Un tentativo di compilare i rapporti di cui sopra un errore:

% javac ParametricException.java 
ParametricException.java:1: a generic class may not extend 
java.lang.Throwable 
class ParametricException<T> extends Exception { // compile-time error 
            ^
1 error 

Questa restrizione è ragionevole perché quasi ogni tentativo di catturare una tale eccezione deve fallire, perché il tipo non è reifiable. Ci si potrebbe aspettare un utilizzo tipico della deroga di essere qualcosa di simile a quanto segue:

class ParametricExceptionTest { 
    public static void main(String[] args) { 
    try { 
     throw new ParametricException<Integer>(42); 
    } catch (ParametricException<Integer> e) { // compile-time error 
     assert e.getValue()==42; 
    } 
    } 
} 

Questo non è consentito, perché il tipo nella clausola catch non è reifiable. Al momento in cui scriviamo, il compilatore Sun riporta una cascata di errori di sintassi in un caso del genere:

% javac ParametricExceptionTest.java 
ParametricExceptionTest.java:5: <identifier> expected 
    } catch (ParametricException<Integer> e) { 
           ^
ParametricExceptionTest.java:8: ')' expected 
    } 
^
ParametricExceptionTest.java:9: '}' expected 
} 
^ 
3 errors 

Poiché le eccezioni non possono essere parametrici, la sintassi è limitato in modo che il tipo deve essere scritto come un identificatore, senza parametro seguente

+2

Cosa intendi quando dici" reifiable "? 'reifiable' non è una parola. – ForYourOwnGood

+1

non conoscevo la parola io, ma una rapida ricerca in google mi ha fatto questo: http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.7 –

+0

https: // docs.oracle.com/javase/tutorial/java/generics/nonReifiableVarargsType.html –

2

Mi aspetterei che sia perché non c'è modo di garantire la parametrizzazione. Considera il seguente codice:

try 
{ 
    doSomethingThatCanThrow(); 
} 
catch (MyException<Foo> e) 
{ 
    // handle it 
} 

Come si nota, la parametrizzazione è solo zucchero sintattico. Tuttavia, il compilatore tenta di garantire che la parametrizzazione rimanga coerente tra tutti i riferimenti a un oggetto nell'ambito della compilazione. Nel caso di un'eccezione, il compilatore non ha modo di garantire che MyException venga solo gettato da un ambito che sta elaborando.

+0

Sì, ma perché non è contrassegnato come "non sicuro", quindi, come per esempio con i calchi? – eljenso

+0

Perché con un cast, stai dicendo al compilatore "So che questo percorso di esecuzione produce il risultato atteso." Con un'eccezione, non puoi dire (per tutte le possibili eccezioni) "So dove è stata lanciata". Ma, come ho detto sopra, è una supposizione; Non ero lì. – kdgregory

+0

"So che questo percorso di esecuzione produce il risultato previsto." Non lo sai, lo speri. Ecco perché i generici e i downcast sono staticamente non sicuri, ma sono comunque consentiti. Ho svalutato la risposta di Torsten, perché lì vedo il problema. Qui non lo faccio. – eljenso

10

È essenzialmente perché è stato progettato in modo negativo.

Questo problema impedisce un disegno astratto pulito ad es.,

public interface Repository<ID, E extends Entity<ID>> { 

    E getById(ID id) throws EntityNotFoundException<E, ID>; 
} 

Il fatto che una clausola di cattura fallirebbe per i generici non sono reificati non è una scusa per quello. Il compilatore potrebbe semplicemente disabilitare i tipi generici concreti che estendono Throwable o impediscono i generici all'interno delle clausole catch.

+0

+1. la mia risposta - http://stackoverflow.com/questions/30759692/throws-x-extends-exception-method-signature/30770099#30770099 – ZhongYu

+0

Esattamente quello che penso –

+1

L'unico modo in cui potevano averlo progettato meglio era il rendering di ~ 10 anni di codice dei clienti incompatibile. Era una decisione commerciale fattibile. Il design era corretto ... ** dato il contesto **. –

3

I farmaci generici vengono controllati in fase di compilazione per la correttezza del tipo. Le informazioni di tipo generico vengono quindi rimosse in un processo chiamato di tipo cancellato. Ad esempio, List<Integer> verrà convertito nel tipo non generico List.

A causa della cancellazione del tipo , i parametri di tipo non possono essere determinati in fase di esecuzione.

Supponiamo che si è permesso di estendere la Throwable in questo modo:

public class GenericException<T> extends Throwable 

Consideriamo ora il seguente codice:

try { 
    throw new GenericException<Integer>(); 
} 
catch(GenericException<Integer> e) { 
    System.err.println("Integer"); 
} 
catch(GenericException<String> e) { 
    System.err.println("String"); 
} 

causa cancellazione del tipo, il runtime non saprà che cattura blocco da eseguire.

Pertanto si tratta di un errore di compilazione se una classe generica è una sottoclasse diretta o indiretta di Throwable.

Fonte:Problems with type erasure

+0

Grazie. Questa è la stessa risposta [quella fornita da Torsten] (https://stackoverflow.com/a/501309/41283). –

+0

No, non lo è. La risposta di Torsten non mi ha aiutato, perché non ha spiegato che tipo di cancellazione/reificazione è. –

Problemi correlati