Geben Sie Erasure in Java Explained ein

1. Übersicht

In diesem kurzen Artikel werden die Grundlagen eines wichtigen Mechanismus in Javas Generika, der als Typlöschung bezeichnet wird, erläutert.

2. Was ist Typlöschung?

Das Löschen von Typen kann als der Prozess des Erzwingens von Typeinschränkungen nur zur Kompilierungszeit und des Verwerfens der Elementtypinformationen zur Laufzeit erklärt werden.

Zum Beispiel:

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

Der Compiler ersetzt den ungebundenen Typ E durch einen tatsächlichen Objekttyp :

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

Daher gewährleistet der Compiler die Typensicherheit unseres Codes und verhindert Laufzeitfehler.

3. Arten der Typlöschung

Das Löschen von Typen kann auf Klassen- (oder Variablen-) und Methodenebene erfolgen.

3.1. Löschen des Klassentyps

Auf Klassenebene verwirft der Compiler die Typparameter für die Klasse und ersetzt sie durch die erste Bindung oder Object, wenn der Typparameter nicht gebunden ist.

Implementieren wir einen Stapel mithilfe eines Arrays:

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

Beim Kompilieren ersetzt der Compiler den ungebundenen Typparameter E durch 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 einem Fall, in dem der Typparameter E gebunden ist:

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

Der Compiler wird den gebundenen Typ - Parameter ersetzt E mit der ersten gebundenen Klasse, vergleichbar in diesem Fall :

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. Löschen des Methodentyps

Beim Löschen von Typen auf Methodenebene wird der Typparameter der Methode nicht gespeichert, sondern in den übergeordneten Typ Object konvertiert, wenn er ungebunden ist, oder in die erste gebundene Klasse, wenn er gebunden ist.

Betrachten wir eine Methode zum Anzeigen des Inhalts eines bestimmten Arrays:

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

Beim Kompilieren ersetzt der Compiler den Typparameter E durch Object :

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

Für einen gebundenen Methodentyp Parameter:

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

Wir werden den Typparameter E löschen und durch Comparable ersetzen lassen :

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

4. Randfälle

Irgendwann während des Löschvorgangs erstellt der Compiler eine Synthesemethode, um ähnliche Methoden zu unterscheiden. Diese können von Methodensignaturen stammen, die dieselbe erste gebundene Klasse erweitern.

Erstellen wir eine neue Klasse, die unsere vorherige Implementierung von Stack erweitert. Bitte beachten Sie, dass sich dies auf die in Abschnitt 3.1 erstellte Stack- Klasse bezieht und nicht auf java.util.Stack .

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

Schauen wir uns nun den folgenden Code an:

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

Nach dem Löschen des Typs haben wir:

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

Beachten Sie, wie wir einen S tring auf den IntegerStack übertragen können, da IntegerStack Push (Object) von der übergeordneten Klasse Stack geerbt hat . Das ist natürlich falsch - wie es eine ganze Zahl sein sollte , da integerStack a - Stack - Typ.

So überrascht es nicht, ein Versuch, Pop einen String und weist auf eine Integer bewirkt eine Classcast aus einem Guss während des eingesetzten Push durch den Compiler.

4.1. Brückenmethoden

Um den obigen Randfall zu lösen, erstellt der Compiler manchmal eine Bridge-Methode. Dies ist eine synthetische Methode, die vom Java-Compiler beim Kompilieren einer Klasse oder Schnittstelle erstellt wird, die eine parametrisierte Klasse erweitert oder eine parametrisierte Schnittstelle implementiert, bei der Methodensignaturen geringfügig unterschiedlich oder mehrdeutig sein können.

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

Wie immer ist der Quellcode, der diesem Artikel beiliegt, auf GitHub verfügbar.