Génériques et effacement de type

10

Les génériques en Java sont implémentés en utilisant l'effacement de type. Le JLS dit que l'inspiration était la compatibilité descendante. Où comme d'autre part les génériques C # sont réifiables.

Théoriquement, quels sont les avantages et les inconvénients d'avoir les génériques comme "effacement" ou "réifiable"?

Java manque-t-il quelque chose?

Jatin
la source

Réponses:

8

Eh bien, la motivation (compatibilité descendante) est à la fois un avantage et un inconvénient. C'est un inconvénient car nous préférerions tous avoir des types réifiables, mais le prix à payer était élevé. Considérez les choix de conception en C #. Ils ont des types réifiables, mais maintenant ils ont des API en double. Imaginez donc une API Java où nous avions également des API en double pour chaque classe paramétrée. Maintenant, imaginez-vous porter des milliers de lignes de code des classes héritées vers les nouvelles classes génériques. Maintenant, qui ne considérerait pas les API en double comme un inconvénient? Mais bon, ils ont des types réifiables!

Ainsi, la motivation principale était "l'évolution, pas la révolution". Et logiquement, chaque décision a des compromis.

Outre les autres inconvénients mentionnés, nous pourrions également ajouter le fait que l'effacement de type peut être difficile à raisonner au moment de la compilation, car il n'est pas évident que certains types seront supprimés, ce qui conduit à des erreurs très étranges et difficiles à trouver.

L'existence de méthodes de pontage (ce compilateur de méthodes générées syntaxiquement pour conserver la compatibilité binaire) peut également être considérée comme un inconvénient. Et cela peut être précisément l'une des raisons des erreurs que j'ai mentionnées dans le paragraphe précédent.

Les inconvénients majeurs découlent du fait déjà évident qu'il existe une seule classe et non plusieurs classes pour les types génériques. Comme autre exemple, considérons que la surcharge d'une méthode avec la même classe générique échoue en Java:

public void doSomething(List<One>);
public void doSomething(List<Two>);

Quelque chose qui pourrait être considéré comme un inconvénient des types réifiables (au moins en C #) est le fait qu'ils provoquent une explosion de code . Par exemple, List<int>est une classe, et a List<double>est une autre totalement différente, car c'est un List<string>et un List<MyType>. Les classes doivent donc être définies au moment de l'exécution, ce qui provoque une explosion des classes et consomme des ressources précieuses lors de leur génération.

Concernant le fait qu'il n'est pas possible de définir un new T()en Java, mentionné dans une autre réponse, il est également intéressant de considérer que ce n'est pas seulement une question d'effacement de type. Cela nécessite également l'existence d'un constructeur par défaut, c'est pourquoi C # nécessite pour cela une "nouvelle contrainte". (Voir Pourquoi le nouveau T () n'est pas possible en Java , par Alex Buckley).

edalorzo
la source
3
Je pense avoir raison de dire que sur le CLR, pour les types de référence T s, vous obtenez une seule copie du Class<T>code pour tous les Ts; plus une copie supplémentaire pour chaque type de valeur T réellement utilisé.
AakashM
@AakashM: c'est correct, il existe une seule classe de type de référence par générique, mais chaque type de valeur crée sa propre version. Cela est dû à la fourniture d'un espace de stockage spécialisé basé sur le type, toutes les références prennent le même stockage mais les types de valeur prennent un stockage différent par type.
Guvante
6

L'inconvénient de l'effacement de type est que vous ne connaissez pas au moment de l'exécution le type du générique. Cela implique que vous ne pouvez pas leur appliquer de réflexion et que vous ne pouvez pas les instancier au moment de l'exécution.

Vous ne pouvez pas faire quelque chose comme ça en Java:

public class MyClass<T> {
    private T t;

    //more methods    
    public void myMethod() {
        //more code
        if (someCondition) {
            t = new T();//illegal
        } else {
            T[] array = new T[];//illegal
        }            
    }       
}

Il existe une solution de contournement pour cela, mais cela nécessite plus de code. L'avantage, comme vous l'avez mentionné, est la compatibilité descendante.

m3th0dman
la source
3

Un autre avantage des génériques effacés est que les différents langages qui se compilent vers la JVM utilisent différentes stratégies pour les génériques, par exemple le site de définition de Scala contre la covariance du site d'utilisation de Java. Les génériques de type supérieur de Scala auraient également été plus difficiles à prendre en charge sur les types réifiés de .Net, car essentiellement Scala sur .Net a ignoré le format de réification incompatible de C #. Si nous avions des génériques réifiés dans la machine virtuelle Java, ces génériques réifiés ne seraient probablement pas adaptés aux fonctionnalités que nous aimons vraiment à propos de Scala, et nous serions coincés avec quelque chose de sous-optimal. Citant le blog d' Ola Bini ,

Cela signifie que si vous souhaitez ajouter des génériques réifiés à la JVM, vous devez être certain que cette implémentation peut englober à la fois tous les langages statiques qui souhaitent innover dans leur propre version de génériques et tous les langages dynamiques qui le souhaitent. créer une bonne implémentation et une belle interface avec les bibliothèques Java. Parce que si vous ajoutez des génériques réifiés qui ne remplissent pas ces critères, vous étoufferez l'innovation et rendrez d'autant plus difficile l'utilisation de la JVM en tant que machine virtuelle multilingue.

Personnellement, je ne considère pas la nécessité d'utiliser un TypeTagcontexte lié à Scala pour les méthodes surchargées conflictuelles comme un inconvénient, car cela déplace les frais généraux et l'inflexibilité de la réification d'un global (programme entier et toutes les langues possibles) à un site d'utilisation par langue question qui est seulement le cas moins fréquemment.

Shelby Moore III
la source
Une autre raison de favoriser les génériques effacés est que les dépendances doivent être injectées d'une manière qui respecte la solution au problème d'expression. Si vous testez des cas spécifiques de types ou codez en dur une usine dans votre fonction générique, alors vous faites mal l'extensibilité.
Shelby Moore III