162 votes

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

Selon le Séparation du langage Java 3ème é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 traduits en Object de toute façon dans le .class Ainsi, déclarer une classe générique revient à dire que tout ce qu'elle contient est un fichier Object . Veuillez me corriger si je me trompe).

2 votes

Les arguments de type générique sont remplacés par la borne supérieure, qui par défaut est Object. Si vous avez quelque chose comme List< ? extends A>, alors A est utilisé dans les fichiers de classe.

0 votes

Merci @Torsten. Je n'avais pas pensé à ce cas de figure auparavant.

2 votes

C'est une bonne question d'entretien, celle-là.

170voto

Torsten Marek Points 27554

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

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

Les deux sites SomeException<Integer> y SomeException<String> sont effacées au même type, il n'y a aucun moyen pour la JVM de distinguer les instances d'exception, et donc aucun moyen de savoir quelle catch doit être exécuté.

5 votes

Mais que signifie "réifiable" ?

1 votes

@aberrant80 : pour faire simple, cela signifie que l'on peut accéder au type concret à l'exécution. Ce qui n'est pas le cas en Java.

72 votes

La règle ne devrait donc pas être "les types génériques ne peuvent pas sous-classer Throwable" mais plutôt "les clauses catch doivent toujours utiliser des types bruts".

16voto

IAdapter Points 15876

Voici un exemple simple de l'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 lance l'exception avec une valeur donnée, qui est rattrapé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 toutes les tentatives d'attraper une telle exception doivent échouer, car le type n'est pas réifiable. On pourrait s'attendre à ce qu'une utilisation typique de l'exception soit quelque chose comme ce qui suit :

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

Ceci n'est pas autorisé, car le type dans la clause catch n'est pas réifiable. Au moment où nous écrivons 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

Comme les exceptions ne peuvent pas être paramétriques, la syntaxe est restreinte de sorte que le type doit être être écrit comme un identificateur, sans paramètre suivant.

2 votes

Que voulez-vous dire par "réifiable" ? "réifiable" n'est pas un mot.

2 votes

Je ne connaissais pas le mot moi-même, mais une recherche rapide dans Google m'a permis de trouver ceci : java.sun.com/docs/books/jls/third_edition/html/

0 votes

15voto

Michele Sollecito Points 161

C'est essentiellement parce qu'il a été conçu d'une mauvaise manière.

Ce problème empêche une 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 car 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 dans les clauses catch.

6voto

outdev Points 3053

Les génériques sont vérifiés à la compilation pour la correction des types. 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 effacement de type 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 ça :

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 effacement de type 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 des caractères

2voto

kdgregory Points 21849

Je pense 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 notez, la paramétrisation n'est qu'un sucre syntaxique. Cependant, le compilateur essaie de s'assurer que le paramétrage reste cohérent sur toutes les références à un objet dans la portée de la compilation. Dans le cas d'une exception, le compilateur n'a aucun moyen de garantir que MonException est uniquement lancée à partir d'une portée qu'il traite.

0 votes

Oui, mais pourquoi n'est-elle pas signalée comme "non sûre", comme les castings par exemple ?

0 votes

Parce qu'avec un cast, vous dites au compilateur "Je sais que ce chemin d'exécution produit le résultat attendu". Avec une exception, vous ne pouvez pas dire (pour toutes les exceptions possibles) "Je sais où ceci a été lancé". Mais, comme je l'ai dit plus haut, c'est une supposition ; je n'étais pas là.

0 votes

"Je sais que ce chemin d'exécution produit le résultat attendu." Vous ne le savez pas, vous l'espérez. C'est pourquoi les génériques et les downcasts ne sont pas statiquement sûrs, mais ils sont néanmoins autorisés. J'ai upvoted la réponse de Torsten, parce que là je vois le problème. Ici, je ne le vois pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X