Perché Java non consente sottoclassi generiche di Throwable?

Secondo Java Language Sepecification , 3a edizione:

È un errore in fase di compilazione se una class generica è una sottoclass diretta o indiretta di Throwable .

Vorrei capire perché questa decisione è stata presa. Cosa c’è di sbagliato con le eccezioni generiche?

(Per quanto ne so, i generici sono semplicemente zucchero sintattico in fase di compilazione, e saranno comunque tradotti in Object nei file .class , quindi dichiarare in modo efficace una class generica è come se tutto ciò che conteneva fosse un Object . Mi sbaglio.)

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

 try { doSomeStuff(); } catch (SomeException e) { // ignore that } catch (SomeException e) { crashAndBurn() } 

Sia SomeException che SomeException vengono cancellati nello stesso tipo, non è ansible per JVM distinguere le istanze di eccezione e quindi non è ansible stabilire quale blocco di catch debba essere eseguito.

Ecco un semplice esempio di come usare l’eccezione:

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

Il corpo dell’istruzione TRy genera l’eccezione con un valore determinato, che viene catturato dalla clausola catch.

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

 class ParametricException 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 il rapporto sopra riporta un errore:

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

Questa restrizione è sensata perché quasi ogni tentativo di catturare tale eccezione deve fallire, perché il tipo non è reifiable. Ci si potrebbe aspettare che un tipico uso dell’eccezione sia qualcosa come il seguente:

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

Questo non è permesso, perché il tipo nella clausola catch non è reifiable. Al momento della stesura di questo, il compilatore Sun riporta una cascata di errori di syntax in questo caso:

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

Poiché le eccezioni non possono essere parametriche, la syntax è limitata in modo che il tipo debba essere scritto come identificatore, senza parametro successivo.

È essenzialmente perché è stato progettato in modo negativo.

Questo problema impedisce un design astratto pulito, ad es.

 public interface Repository> { E getById(ID id) throws EntityNotFoundException; } 

Il fatto che una clausola di cattura fallisca per i generici non è reificata non è una scusa per questo. Il compilatore potrebbe semplicemente non consentire i tipi generici concreti che estendono Throwable o impediscono i generici all’interno delle clausole catch.

I generici vengono controllati in fase di compilazione per la correttezza del tipo. Le informazioni di tipo generico vengono quindi rimosse in un processo chiamato cancellazione di tipi . Ad esempio, l’ List verrà convertito List tipi non generici.

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

Supponiamo che tu possa estendere Throwable questo modo:

 public class GenericException extends Throwable 

Ora consideriamo il seguente codice:

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

A causa della cancellazione dei tipi , il runtime non saprà quale blocco catch eseguire.

Pertanto è un errore in fase di compilazione se una class generica è una sottoclass diretta o indiretta di Throwable.

Fonte: problemi con la cancellazione dei tipi

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

 try { doSomethingThatCanThrow(); } catch (MyException 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 object 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.