Java génériques type d'effacement: quand et ce qui se passe?

j'ai lu à propos de Java type erasure sur le site Web D'Oracle .

quand l'effacement de type se produit-il? à l'Heure de la compilation ou de l'exécution? Lorsque la classe est chargée? Lorsque la classe est instanciée?

beaucoup de sites (y compris le tutoriel officiel mentionné ci-dessus) disent que l'effacement de type se produit au moment de la compilation. Si les informations de type sont complètement supprimées au moment de la compilation, comment le JDK vérifie-t-il le type compatibilité lorsqu'une méthode utilisant generics est invoquée avec aucune information de type ou des informations de type incorrectes?

prenons l'exemple suivant: Say class A a une méthode, empty(Box<? extends Number> b) . Nous compilons A.java et obtenons le fichier de classe A.class .

public class A {
    public static void empty(Box<? extends Number> b) {}
}
public class Box<T> {}

maintenant nous créons une autre classe B qui invoque la méthode empty avec un argument non paramétrisé (type brut): empty(new Box()) . Si nous compilez B.java avec A.class dans la classepath, javac est assez intelligent pour émettre un avertissement. Donc A.class a quelque information de type stockée dans elle.

public class B {
    public static void invoke() {
        // java: unchecked method invocation:
        //  method empty in class A is applied to given types
        //  required: Box<? extends java.lang.Number>
        //  found:    Box
        // java: unchecked conversion
        //  required: Box<? extends java.lang.Number>
        //  found:    Box
        A.empty(new Box());
    }
}

j'imagine que le type d'effacement se produit lorsque la classe est chargée, mais c'est juste une supposition. Alors quand est-ce arrivé?

209
demandé sur Radiodef 2008-12-04 09:13:02

7 réponses

Type d'effacement s'applique à la utiliser des génériques. Il y a certainement des métadonnées dans le fichier de classe pour dire si oui ou non une méthode/type est générique, et quelles sont les contraintes etc. Mais quand les génériques sont utilisés , ils sont convertis en contrôles de compilation et de temps d'exécution. Alors ce code:

List<String> list = new ArrayList<String>();
list.add("Hi");
String x = list.get(0);

est compilé en

List list = new ArrayList();
list.add("Hi");
String x = (String) list.get(0);

au moment de l'exécution il n'y a aucun moyen de découvrir que T=String pour l'objet de la liste - cette information a disparu.

... mais l'interface List<T> elle-même s'annonce encore comme étant Générique.

éditer: juste pour clarifier, le compilateur ne conserve les informations sur la variable étant un List<String> - mais vous ne pouvez toujours pas trouver que T=String pour l'objet liste elle-même.

210
répondu Jon Skeet 2013-06-06 01:21:56

le compilateur est responsable de la compréhension des génériques au moment de la compilation. Le compilateur est également responsable de jeter cette "compréhension" des classes génériques, dans un processus que nous appelons "151910920 de type" effacement . Tout se passe au moment de la compilation.

Note: Contrairement aux croyances de la majorité des développeurs Java, il est possible de conserver des informations de type compilation-time et de récupérer ces informations à l'exécution, malgré dans un très manière limitée. En d'autres termes: Java fournit des génériques reified d'une manière très restreinte .

concernant l'effacement de type

noter que, au moment de la compilation, le compilateur dispose de toutes les informations de type disponibles, mais cette information est volontairement abandonné en général lorsque le code octet est généré, dans un processus connu sous le nom type erasure . C'est fait de cette façon en raison de la compatibilité questions: l'intention des concepteurs du langage était de fournir une compatibilité complète du code source et du code octet entre les versions de la plate-forme. Si elle était implémentée différemment, vous auriez à recompiler vos applications héritées lors de la migration vers les nouvelles versions de la plate-forme. La façon dont il a été fait, toutes les signatures de méthode sont préservées (compatibilité du code source) et vous n'avez pas besoin de recompiler quoi que ce soit (compatibilité binaire).

concernant reified génériques en Java

si vous devez conserver des informations de type Compilation, vous devez employer des classes anonymes. Le fait est que, dans le cas très particulier des classes anonymes, il est possible de récupérer des informations complètes de type Compilation à l'exécution, ce qui, en d'autres termes, signifie: génériques reified. Cela signifie que le compilateur ne jette pas l'information de type lorsque des classes anonymes sont impliquées; cette information est conservée dans le code binaire généré et le système d'exécution vous permet de récupérer cette information.

j'ai écrit un article à ce sujet:

http://rgomes-info.blogspot.co.uk/2013/12/using-typetokens-to-retrieve-generic.html

Une remarque à propos de la technique décrite dans l'article ci-dessus est que la technique est obscur pour la majorité des développeurs. En dépit de cela fonctionne et fonctionne bien, la plupart des développeurs se sentent confus ou mal à l'aise avec la technique. Si vous avez une base de code partagée ou si vous prévoyez de publier votre code au public, Je ne recommande pas la technique ci-dessus. D'autre part, si vous êtes l'unique utilisateur de votre code, vous pouvez profiter de la puissance de cette technique offre à vous.

code échantillon

l'article ci-dessus a des liens avec le code de l'échantillon.

87
répondu Richard Gomes 2016-01-26 11:12:50

si vous avez un champ qui est un type générique, ses paramètres de type sont compilés dans la classe.

Si vous avez une méthode qui prend ou retourne un type générique, ces paramètres de type sont compilés dans la classe.

cette information est ce que le compilateur utilise pour vous dire que vous ne pouvez pas passer un Box<String> à la méthode empty(Box<T extends Number>) .

L'API est compliquée, mais vous pouvez inspecter ce type d'informations à travers le API de réflexion avec des méthodes comme getGenericParameterTypes , getGenericReturnType , et, pour les champs, getGenericType .

Si vous avez un code qui utilise un type générique, le compilateur insère jette comme nécessaire (l'appelant) pour vérifier les types. Les objets génériques eux-mêmes ne sont que le type brut; le type paramétré est "effacé". Donc, quand vous créez un new Box<Integer>() , il n'y a aucune information sur la classe Integer dans l'objet Box .

Angelika Langer's FAQ est la meilleure référence que j'ai vu pour les génériques Java.

31
répondu erickson 2008-12-04 06:50:05

Génériques en Langage Java est un très bon guide sur ce sujet.

génériques sont mis en œuvre par Java le compilateur comme une conversion frontale appelé à l'effacement. Vous pouvez (presque) pense que comme une source-à-source traduction, par lequel le générique la version de loophole() est convertie en la version non générique.

Donc, c'est au moment de la compilation. La JVM ne saura jamais ArrayList que vous avez utilisé.

je recommande aussi la réponse de M. Skeet sur Quelle est la notion d'effacement dans les génériques en Java?

11
répondu Eugene Yokota 2017-06-08 19:45:18
L'effacement de Type

se produit au moment de la compilation. Ce que signifie type erasure est qu'il oubliera le type générique, pas tous les types. En outre, il y aura toujours des métadonnées sur les types génériques. Par exemple

Box<String> b = new Box<String>();
String x = b.getDefault();

est converti en

Box b = new Box();
String x = (String) b.getDefault();

au moment de la compilation. Vous pouvez obtenir des avertissements non pas parce que le compilateur sait de quel type est le générique de, mais au contraire, parce qu'il ne sait pas assez de sorte qu'il ne peut pas type de garantie sécurité.

de plus, le compilateur conserve les informations de type sur les paramètres d'un appel de méthode, que vous pouvez récupérer par réflexion.

Ce guide est le meilleur que j'ai trouvé sur le sujet.

6
répondu Vinko Vrsalovic 2008-12-04 06:40:54

j'ai rencontré le type erasure sur Android. En production, nous utilisons gradle avec minify option. Après minification, j'ai l'exception fatale. J'ai fait simple fonction pour montrer chaîne d'héritage de mon objet:

public static void printSuperclasses(Class clazz) {
    Type superClass = clazz.getGenericSuperclass();

    Log.d("Reflection", "this class: " + (clazz == null ? "null" : clazz.getName()));
    Log.d("Reflection", "superClass: " + (superClass == null ? "null" : superClass.toString()));

    while (superClass != null && clazz != null) {
        clazz = clazz.getSuperclass();
        superClass = clazz.getGenericSuperclass();

        Log.d("Reflection", "this class: " + (clazz == null ? "null" : clazz.getName()));
        Log.d("Reflection", "superClass: " + (superClass == null ? "null" : superClass.toString()));
    }
}

Et il y a deux résultats de cette fonction:

code non réduit:

D/Reflection: this class: com.example.App.UsersList
D/Reflection: superClass: com.example.App.SortedListWrapper<com.example.App.Models.User>

D/Reflection: this class: com.example.App.SortedListWrapper
D/Reflection: superClass: android.support.v7.util.SortedList$Callback<T>

D/Reflection: this class: android.support.v7.util.SortedList$Callback
D/Reflection: superClass: class java.lang.Object

D/Reflection: this class: java.lang.Object
D/Reflection: superClass: null

code abrégé:

D/Reflection: this class: com.example.App.UsersList
D/Reflection: superClass: class com.example.App.SortedListWrapper

D/Reflection: this class: com.example.App.SortedListWrapper
D/Reflection: superClass: class android.support.v7.g.e

D/Reflection: this class: android.support.v7.g.e
D/Reflection: superClass: class java.lang.Object

D/Reflection: this class: java.lang.Object
D/Reflection: superClass: null

ainsi, en code réduit classes paramétrées réelles sont remplacés par des types de classes brutes sans aucune information sur les types. Comme solution pour mon projet j'ai supprimé tous les appels de réflexion et les ai replantés avec des types de params explicites passés dans les arguments de fonction.

2
répondu porfirion 2016-10-14 12:47:33

le terme" type erasure " n'est pas vraiment la description correcte du problème de Java avec des génériques. Type erasure n'est pas en soi une mauvaise chose, en effet, il est très nécessaire pour la performance et est souvent utilisé dans plusieurs langues comme C++, Haskell, D.

avant de vous dégoût, s'il vous plaît rappeler la définition correcte de type erasure de Wiki

Qu'est-ce que le type erasure?

effacement de type fait référence au processus de charge-Temps par lequel les annotations de type explicites sont retirées d'un programme, avant qu'il ne soit exécuté à l'exécution

erasure de Type signifie Jeter des étiquettes de type créées au moment de la conception ou des étiquettes de type déduites au moment de la compilation de sorte que le programme compilé en code binaire ne contient pas d'étiquettes de type. Et c'est le cas pour tous les langages de programmation compilant du code binaire sauf dans certains cas où vous avez besoin d'étiquettes runtime. Ces exceptions incluent par exemple tous les types existentiels (types de référence Java qui sont sous-typables, N'importe quel Type dans de nombreuses langues, types D'Union). La raison de l'effacement de type est que les programmes se transforment en un langage qui est en quelque sorte Uni-typé (langage binaire ne permettant que les bits) que les types sont des abstractions seulement et d'affirmer une structure pour ses valeurs et la sémantique appropriée pour les manipuler.

donc c'est en retour, une quelque chose de naturel.

le problème de Java est différent et causé par la façon dont il se reifie.

les déclarations souvent faites au sujet de Java n'a pas reified génériques est également faux.

Java ne reify, mais d'une manière erronée en raison de la rétrocompatibilité.

Qu'est-ce que la réification?

de notre Wiki

La réification est le processus par lequel une idée abstraite d'un programme informatique est transformée en un modèle de données explicite ou un autre objet créé dans un langage de programmation.

réification signifie transformer quelque chose d'abstrait (Type paramétrique) en quelque chose de concret (Type Béton) par spécialisation.

nous l'illustrons par un exemple simple:

un ArrayList avec définition:

ArrayList<T>
{
    T[] elems;
    ...//methods
}

est une abstraction, en détail un constructeur de type, qui obtient "reified" lorsqu'il est spécialisé avec un type de béton, dire entier:

ArrayList<Integer>
{
    Integer[] elems;
}

ArrayList<Integer> est vraiment un type.

mais c'est exactement la chose ce que Java ne fait pas!!! , au lieu de cela ils réifient constamment les types abstraits avec leurs limites, c.-à-d. produisant le même type de béton indépendant des paramètres passés dans pour spécialisation:

ArrayList
{
    Object[] elems;
}

qui est ici réifié avec l'objet lié implicite ( ArrayList<T extends Object> == ArrayList<T> ).

malgré qu'il rend les tableaux génériques inutilisables et cause quelques erreurs étranges pour les types bruts:

List<String> l= List.<String>of("h","s");
List lRaw=l
l.add(new Object())
String s=l.get(2) //Cast Exception

il provoque beaucoup d'ambiguïtés

void function(ArrayList<Integer> list){}
void function(ArrayList<Float> list){}
void function(ArrayList<String> list){}

se référer à la même fonction:

void function(ArrayList list)

et donc surcharge de la méthode générique ne peut pas être utilisé en Java.

1
répondu iconfly 2018-07-31 12:50:26