web-dev-qa-db-fra.com

Pourquoi Java n'autorise-t-il pas les sous-classes génériques de Throwable?

Selon la Java Language Sepecification , 3e édition:

C'est une erreur de compilation si une classe générique est une sous-classe directe ou indirecte de Throwable.

Je souhaite comprendre pourquoi cette décision a été prise. Quel est le problème avec les exceptions génériques?

(Pour autant que je sache, les génériques sont simplement du sucre syntaxique à la compilation, et ils seront de toute façon traduits en Object dans le .class fichiers, donc déclarer une classe générique est comme si tout y était un Object. Corrigez-moi si j'ai tort, s'il-vous plait.)

138
Hosam Aly

Comme l'a dit Mark, les types ne sont pas réifiables, ce qui pose problème dans le cas suivant:

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

Tous les deux SomeException<Integer> et SomeException<String> sont effacées du même type, il n'y a aucun moyen pour la JVM de distinguer les instances d'exception, et donc aucun moyen de dire quel bloc catch doit être exécuté.

148
Torsten Marek

Voici un exemple simple d'utilisation de l'exception:

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

Le corps de l'instruction TRy lève l'exception avec une valeur donnée, qui est interceptée par la clause catch.

En revanche, la définition suivante d'une nouvelle exception est interdite, car elle crée un type paramétré:

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

Une tentative de compilation de ce qui précède signale une erreur:

% 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

Cette restriction est judicieuse car presque toute tentative d'intercepter une telle exception doit échouer, car le type n'est pas réifiable. On pourrait s'attendre à ce qu'une utilisation typique de l'exception soit la suivante:

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

Cela n'est pas autorisé, car le type de la clause catch n'est pas réifiable. Au moment d'écrire ces lignes, le compilateur Sun signale une cascade d'erreurs de syntaxe dans un tel cas:

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

Étant donné que les exceptions ne peuvent pas être paramétriques, la syntaxe est restreinte de sorte que le type doit être écrit en tant qu'identifiant, sans paramètre suivant.

14
IAdapter

C'est essentiellement parce qu'il a été mal conçu.

Ce problème empêche la conception abstraite propre, par exemple,

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

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

Le fait qu'une clause catch échouerait pour les génériques ne sont pas réifiés n'est pas une excuse pour cela. Le compilateur pourrait simplement interdire les types génériques concrets qui étendent Throwable ou interdire les génériques à l'intérieur des clauses catch.

12
Michele Sollecito

Les génériques sont vérifiés au moment de la compilation pour l'exactitude du type. Les informations de type générique sont ensuite supprimées dans un processus appelé effacement de type. Par exemple, List<Integer> sera converti en type non générique List.

En raison de type erasure, les paramètres de type ne peuvent pas être déterminés au moment de l'exécution.

Supposons que vous êtes autorisé à étendre Throwable comme ceci:

public class GenericException<T> extends Throwable

Considérons maintenant le code suivant:

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

En raison de type erasure, le runtime ne saura pas quel bloc catch exécuter.

C'est donc une erreur de compilation si une classe générique est une sous-classe directe ou indirecte de Throwable.

Source: Problèmes d'effacement de type

4
outdev

Je suppose que c'est parce qu'il n'y a aucun moyen de garantir le paramétrage. Considérez le code suivant:

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

Comme vous le constatez, le paramétrage n'est que du sucre syntaxique. Cependant, le compilateur essaie de s'assurer que le paramétrage reste cohérent dans toutes les références à un objet dans la portée de compilation. Dans le cas d'une exception, le compilateur n'a aucun moyen de garantir que MyException est uniquement levée à partir d'une étendue qu'il traite.

2
kdgregory