Digitare Erasure in Java Explained

1. Panoramica

In questo rapido articolo, discuteremo le basi di un importante meccanismo nei generici di Java noto come cancellazione del tipo.

2. Che cos'è la cancellazione del tipo?

La cancellazione del tipo può essere spiegata come il processo di applicazione dei vincoli di tipo solo in fase di compilazione e di eliminazione delle informazioni sul tipo di elemento in fase di esecuzione.

Per esempio:

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

Il compilatore sostituisce il tipo E non associato con un tipo effettivo di Object :

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

Pertanto il compilatore garantisce l'indipendenza dai tipi del nostro codice e previene gli errori di runtime.

3. Tipi di cancellazione del tipo

La cancellazione del tipo può avvenire a livello di classe (o variabile) e metodo.

3.1. Cancellazione del tipo di classe

A livello di classe, il compilatore scarta i parametri di tipo sulla classe e li sostituisce con il suo primo limite o Object se il parametro di tipo non è associato .

Implementiamo uno Stack usando un array:

public class Stack { private E[] stackContent; public Stack(int capacity) { this.stackContent = (E[]) new Object[capacity]; } public void push(E data) { // .. } public E pop() { // .. } }

Al momento della compilazione, il compilatore sostituisce il parametro di tipo non associato E con Object :

public class Stack { private Object[] stackContent; public Stack(int capacity) { this.stackContent = (Object[]) new Object[capacity]; } public void push(Object data) { // .. } public Object pop() { // .. } }

In un caso in cui il parametro di tipo E è vincolato:

public class BoundStack
    
      { private E[] stackContent; public BoundStack(int capacity) { this.stackContent = (E[]) new Object[capacity]; } public void push(E data) { // .. } public E pop() { // .. } }
    

Il compilatore sostituirà il parametro di tipo associato E con la prima classe associata, comparabile in questo caso :

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. Cancellazione del tipo di metodo

Per la cancellazione del tipo a livello di metodo, il parametro di tipo del metodo non viene memorizzato ma piuttosto convertito nel suo tipo genitore Object se non è associato o è la prima classe associata quando è associato.

Consideriamo un metodo per visualizzare il contenuto di un dato array:

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

Al momento della compilazione, il compilatore sostituisce il parametro di tipo E con Object :

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

Per un parametro del tipo di metodo associato:

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

Avremo il parametro di tipo E cancellato e sostituito con Comparable:

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

4. Casi Edge

A volte durante il processo di cancellazione del tipo, il compilatore crea un metodo sintetico per differenziare metodi simili. Questi possono provenire da firme di metodo che estendono la stessa prima classe associata.

Creiamo una nuova classe che estenda la nostra precedente implementazione di Stack. Si noti che questo si riferisce alla classe Stack che abbiamo creato nella sezione 3.1 e non a java.util.Stack .

public class IntegerStack extends Stack { public IntegerStack(int capacity) { super(capacity); } public void push(Integer value) { super.push(value); } }

Ora diamo un'occhiata al seguente codice:

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

Dopo la cancellazione del tipo, abbiamo:

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

Notate come siamo in grado di spingere un S tring sul IntegerStack - perché IntegerStack ereditato push (Object) dalla classe padre Stack . Questo è, ovviamente, errato, poiché dovrebbe essere un numero intero poiché integerStack è un tipo Stack .

Quindi, non sorprendentemente, un tentativo di pop una stringa e assegnare a un numero intero provoca un ClassCastException da un calco inserita durante la spinta dal compilatore.

4.1. Metodi di bridge

Per risolvere il caso limite sopra, il compilatore a volte crea un metodo bridge. Questo è un metodo sintetico creato dal compilatore Java durante la compilazione di una classe o di un'interfaccia che estende una classe parametrizzata o implementa un'interfaccia parametrizzata in cui le firme del metodo possono essere leggermente diverse o ambigue.

In our example above, the Java compiler preserves polymorphism of generic types after erasure by ensuring no method signature mismatch between IntegerStack‘s push(Integer) method and Stack‘s push(Object) method.

Hence, the compiler creates a bridge method here:

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

Consequently, Stack class's push method after type erasure, delegates to the original push method of IntegerStack class.

5. Conclusion

In this tutorial, we've discussed the concept of type erasure with examples in type parameter variables and methods.

You can read more about these concepts:

  • Java Language Specification: Type Erasure
  • The Basics of Java Generics

Come sempre, il codice sorgente che accompagna questo articolo è disponibile su GitHub.