2009-02-01 6 views
116

Selon le Java Language Sepecification, 3ème édition:Pourquoi Java n'autorise-t-il pas les sous-classes génériques de Throwable?

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

Je veux 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 médicaments génériques sont la compilation simplement du sucre syntaxique, et ils seront traduits en Object de toute façon dans les .class fichiers, afin de déclarer efficacement une classe générique est comme si tout était un Object. S'il vous plaît corrigez-moi si je me trompe)

+1

arguments de type générique sont remplacés par la limite supérieure, qui par défaut est l'objet. Si vous avez quelque chose comme la liste , alors A est utilisé dans les fichiers de classe. –

+0

Merci @Torsten. Je n'ai pas pensé à ce cas auparavant. –

+2

C'est une bonne question d'interview, celle-ci. – skaffman

Répondre

128

Comme marque dit, les types ne sont pas réifiable, ce qui est un problème dans le cas suivant:

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

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

+1

mais que signifie "réifiable"? – aberrant80

+1

@ aberrant80: simplement, cela signifie que vous pouvez accéder au type concret à l'exécution. Ce qui n'est pas le cas en Java. –

+34

Donc la règle ne devrait pas être "les types génériques ne peuvent pas sous-classer Throwable" mais plutôt "les clauses catch doivent toujours utiliser des types raw". – Archie

13

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 lève l'exception d'une valeur donnée, qui est pris par la clause de capture.

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

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 compiler les rapports ci-dessus 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 raisonnable parce que presque toute tentative d'attraper une telle exception doit échouer, parce que le type n'est pas reifiable. On pourrait attendre une utilisation typique de l'exception à être 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; 
    } 
    } 
} 

Ce n'est pas autorisé, car le type dans la clause de capture ne sont 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 

Parce que les exceptions ne peuvent pas être paramétrique, la syntaxe est limitée afin que le type doit être écrit sous la forme d'un identifiant, sans paramètre suivant.

+2

Que voulez-vous dire quand vous dites 'reifiable'? 'reifiable' n'est pas un autre mot. – ForYourOwnGood

+1

Je ne connaissais pas le mot moi-même, mais une recherche rapide dans google m'a eu ceci: 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

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 remarquez, le paramétrage est juste du sucre syntaxique. Toutefois, 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 lancé à partir d'une portée qu'il traite.

+0

Oui, mais pourquoi n'est-il pas marqué comme "dangereux" alors, comme pour les moulages par exemple? – eljenso

+0

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ù cela a été jeté." Mais, comme je le dis plus haut, c'est une supposition; Je n'étais pas là. – kdgregory

+0

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

10

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

Ce problème empêche la conception abstraite propre, par ex.Le fait qu'une clause catch échoue pour les génériques n'est pas réifié, ce qui n'est pas une excuse pour cela. Le compilateur pourrait simplement interdire les types génériques concrets qui étendent les génériques Throwable ou Disallow à l'intérieur des clauses catch.

+0

+1. ma réponse - http://stackoverflow.com/questions/30759692/throws-x-extends-exception-method-signature/30770099#30770099 – ZhongYu

+0

Exactement ce que je pense –

+1

La seule façon dont ils pourraient avoir mieux conçu était de rendre ~ 10 années de code des clients incompatibles. C'était une décision d'affaires viable. Le design était correct ... ** compte tenu du contexte **. –

3

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

En raison de type d'effacement, 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 effacement de type, le moteur d'exécution ne saura pas quelle prise bloquer pour exécuter.

Par conséquent, il s'agit d'une erreur de compilation si une classe générique est une sous-classe directe ou indirecte de Throwable.

Source:Problems with type erasure

+0

Merci. C'est la même réponse que [celle fournie par Torsten] (https://stackoverflow.com/a/501309/41283). –

+0

Non ce n'est pas. La réponse de Torsten ne m'a pas aidé, car elle n'a pas expliqué quel type d'effacement/réification est. –

Questions connexes