Type Erasure in Java Explained

Effacement de type dans Java expliqué

1. Vue d'ensemble

Dans cet article rapide, nous aborderons les bases d’un mécanisme important des génériques Java appelé effacement de type.

2. Qu'est-ce que l'effacement de type?

L'effacement de type peut être expliqué comme le processus consistant à appliquer des contraintes de type uniquement au moment de la compilation et à rejeter les informations de type d'élément au moment de l'exécution.

Par exemple:

public static   boolean containsElement(E [] elements, E element){
    for (E e : elements){
        if(e.equals(element)){
            return true;
        }
    }
    return false;
}

Une fois compilé, le type non liéE est remplacé par un type réel deObject:

public static  boolean containsElement(Object [] elements, Object element){
    for (Object e : elements){
        if(e.equals(element)){
            return true;
        }
    }
    return false;
}

Le compilateur assure la sécurité de type de notre code et évite les erreurs d’exécution.

3. Types d'effacement de type

Le type peut être effacé aux niveaux de la classe (ou de la variable) et de la méthode.

3.1. Effacement de type de classe

Au niveau de la classe, les paramètres de type de la classe sont ignorés lors de la compilation du code et remplacés par sa première borne, ouObject si le paramètre de type n'est pas lié.

Implémentons unStack en utilisant un tableau:

public class Stack {
    private E[] stackContent;

    public Stack(int capacity) {
        this.stackContent = (E[]) new Object[capacity];
    }

    public void push(E data) {
        // ..
    }

    public E pop() {
        // ..
    }
}

Lors de la compilation, le paramètre de type non liéE est remplacé parObject:

public class Stack {
    private Object[] stackContent;

    public Stack(int capacity) {
        this.stackContent = (Object[]) new Object[capacity];
    }

    public void push(Object data) {
        // ..
    }

    public Object pop() {
        // ..
    }
}

Dans un cas où le paramètre de typeE est lié:

public class BoundStack> {
    private E[] stackContent;

    public BoundStack(int capacity) {
        this.stackContent = (E[]) new Object[capacity];
    }

    public void push(E data) {
        // ..
    }

    public E pop() {
        // ..
    }
}

Une fois compilé, le paramètre de type liéE est remplacé par la première classe liée,Comparable dans ce cas:

public class BoundStack {
    private Comparable [] stackContent;

    public BoundStack(int capacity) {
        this.stackContent = (Comparable[]) new Object[capacity];
    }

    public void push(Comparable data) {
        // ..
    }

    public Comparable pop() {
        // ..
    }
}

3.2. Effacement du type de méthode

Pour l’effacement de type au niveau de la méthode, le paramètre de type de la méthode n’est pas stocké, mais plutôt converti en son type parentObject s’il n’est pas lié ou il s’agit de la première classe liée lorsqu'elle est liée.

Considérons une méthode pour afficher le contenu d'un tableau donné:

public static  void printArray(E[] array) {
    for (E element : array) {
        System.out.printf("%s ", element);
    }
}

Lors de la compilation, le paramètre de typeE est remplacé parObject:

public static void printArray(Object[] array) {
    for (Object element : array) {
        System.out.printf("%s ", element);
    }
}

Pour un paramètre de type de méthode lié:

public static > void printArray(E[] array) {
    for (E element : array) {
        System.out.printf("%s ", element);
    }
}

Nous allons faire effacer le paramètre de typeE et le remplacer parComparable:

public static void printArray(Comparable[] array) {
    for (Comparable element : array) {
        System.out.printf("%s ", element);
    }
}

4. Étuis Edge

Au cours du processus d’effacement des types, le compilateur crée une méthode synthétique pour différencier les méthodes similaires. Celles-ci peuvent provenir de signatures de méthodes étendant la même première classe liée.

Créons une nouvelle classe qui étend notre implémentation précédente deStack:

public class IntegerStack extends Stack {

    public IntegerStack(int capacity) {
        super(capacity);
    }

    public void push(Integer value) {
        super.push(value);
    }
}

Examinons maintenant le code suivant:

IntegerStack integerStack = new IntegerStack(5);
Stack stack = integerStack;
stack.push("Hello");
Integer data = integerStack.pop();

Après l'effacement du type, nous avons:

IntegerStack integerStack = new IntegerStack(5);
Stack stack = (IntegerStack) integerStack;
stack.push("Hello");
Integer data = (String) integerStack.pop();

Remarquez comment nous pouvons pousser unString sur leIntegerStack - parce queIntegerStack a hérité depush(Object) de la classe parentStack. Ceci est, bien sûr, incorrect - car il devrait s'agir d'un entier puisqueintegerStack est un typeStack<Integer>.

Donc, sans surprise, une tentative depop aString et d'affecter unInteger provoque unClassCastException à partir d'un cast inséré pendant lespush par le compilateur.

4.1. Méthodes de pont

Pour résoudre le cas ci-dessus, le compilateur crée parfois une méthode de pontage. Il s'agit d'une méthode synthétique créée par le compilateur Java lors de la compilation d'une classe ou d'une interface qui étend une classe paramétrée ou implémente une interface paramétrée dans laquelle les signatures de méthode peuvent être légèrement différentes ou ambiguës.

Dans notre exemple ci-dessus, le compilateur Java préserve le polymorphisme des types génériques après l'effacement en garantissant l'absence de discordance de signature de méthode entre la méthodepush(Integer) deIntegerStack et la méthodepush(Object) deStack.

Par conséquent, le compilateur crée une méthode de pont ici:

public class IntegerStack extends Stack {
    // Bridge method generated by the compiler

    public void push(Object value) {
        push((Integer)value);
    }

    public void push(Integer value) {
        super.push(value);
    }
}

Par conséquent, la méthodepush de la classeStack après l'effacement du type, délègue à la méthodepush d'origine de la classeIntegerStack.

5. Conclusion

Dans ce didacticiel, nous avons abordé le concept d'effacement de type avec des exemples de variables et de méthodes de paramètre de type.

Vous pouvez en savoir plus sur ces concepts:

Comme toujours, le code source qui accompagne cet article est disponibleover on GitHub.