Generika Java
JDK 1.5 führte einige Erweiterungen der Java-Sprache ein . Eine davon ist die Einführung von Generika oder generischen Typen . Ein Generikum ist ein Werkzeug, das die Definition eines parametrisierten Typs ermöglicht, was später in der Kompilierungsphase bei Bedarf erklärt wird; Mit Generika können Sie Abstraktionen für die in der Sprache definierten Datentypen definieren.
Eigenschaften
Die Verwendung von Generika hat mehrere Vorteile:
- Bietet eine bessere Verwaltung der Typprüfung während der Kompilierung;
- Vermeiden Sie das Casting von Object. Dh ;
- Vermeiden Sie Fehler durch unsachgemäßes Gießen
Anstatt (Code, der einen Casting-Fehler auslösen könnte) zu verwenden:
String Titel = (( String ) Wörter . Get ( i )). toUppercase ();
oder richtiger, um Fehler zu vermeiden
Objekt o = Wörter . bekomme ( ich );
Zeichenfolgentitel = " " ; if ( oder instanceof String ) title = (( String ) o . get ( i )). toUppercase ();
verwendet werden:
Zeichenfolgentitel = Wörter . _ bekomme ( ich ). toUppercase ();
Allerdings gibt es auch Nachteile:
Es ist definiert:
Liste < String > Wörter = neue ArrayList < String > ();
Anstatt von:
Wörter auflisten = neue ArrayList ();
Das häufigste Beispiel für ihre Verwendung ist die Definition / Verwendung von sogenannten Containern . Vor der Veröffentlichung von JDK 1.5 musste zur transparenten Verwaltung verschiedener Datentypen darauf zurückgegriffen werden, dass in Java jede Klasse implizit von der Objektklasse abgeleitet ist . Wenn Sie beispielsweise eine verknüpfte Liste implementieren mussten, lautete der Code wie folgt:
List myIntList = new LinkedList ();
meineIntListe . add ( neue Ganzzahl ( 0 ));
und um stattdessen das gerade eingefügte Element abzurufen, mussten Sie schreiben
Ganzzahl x = ( Ganzzahl ) meineIntListe . Iterator (). nächste ();
Beachten Sie die Umwandlung in Integer , die erforderlich ist, da myIntList tatsächlich mit Object- Objekten arbeitet . Seit der Einführung von JDK 1.5 ist es stattdessen möglich, einen Code wie den folgenden zu verwenden:
List < Integer > myIntList = new LinkedList < Integer > ();
meineIntListe . add ( neue Ganzzahl ( 0 ));
wo ausdrücklich angegeben ist, dass die myIntList nur mit Objekten vom Typ Integer funktioniert . Um das gerade eingefügte Element abzurufen, lautet der Code wie folgt:
Ganzzahl x = meineIntListe . Iterator (). nächste ();
Beachten Sie, dass die Umwandlung jetzt nicht mehr erforderlich ist, da die Liste aus ganzen Zahlen besteht.
Umsetzung
Java 5 hat die Bytecode -Sprache nicht erweitert , um Generika zu implementieren. Das bedeutet, dass Generics eigentlich nur syntaktische Konstrukte sind, die auf Bytecode-Ebene durch den üblichen Mechanismus der Object-Klasse (oben beschrieben) emuliert werden. [1] Erklären
List < Integer > myIntList = new LinkedList < Integer > ();
es ist programmatisch äquivalent zu deklarieren
List myIntList = new LinkedList (); // Objektliste
und um implizit Object-> Integer und Integer-> Object Konvertierungen durchzuführen , um Elemente zu lesen und zu schreiben.
Generika haben also die Typisierungsprobleme beseitigt; jetzt müssen die Elemente der Liste Integer und nicht (zum Beispiel) String sein , und diese Prüfung wird zur Kompilierzeit durchgeführt.
Löschen
Löschen ist der Prozess, der das mit Generika codierte Programm in die Form ohne sie umwandelt, die den erzeugten Bytecode am ehesten widerspiegelt . Dieser Begriff ist nicht ganz korrekt, da Generika entfernt, aber auch Casts hinzugefügt werden. Das Hinzufügen dieser Casts ist nicht explizit und die Projektsprache bietet die Cast-Iron-Garantie : das heißt, die implizite Cast, die der Kompilierung von Generika hinzugefügt wird: Sie kann niemals fehlschlagen. Dies ist eine Regel, die für Code gilt, der keine ungeprüften Warnungen enthält . Die Vorteile der Implementierung über Erasure sind:
- halten Sie die Dinge einfach, ohne Details oder irgendetwas anderes hinzuzufügen;
- halten Sie die Dinge klein, zum Beispiel mit nur einer Implementierung von List;
- Um die Weiterentwicklung zu vereinfachen, kann von generischem Code und Legacy-Code auf dieselbe Bibliothek zugegriffen werden.
Wenn ein Element Y von einem Element X abgeleitet ist, kann nicht gesagt werden, dass eine Sammlung von Elementen von Y von der Sammlung von Elementen X abgeleitet ist, da dies im Allgemeinen eine unmögliche Operation ist; nur ein Teil davon kann sogar sicher sein, insbesondere in Bezug auf Arrays und Lesen, aber das gilt nicht im Allgemeinen.
Betrachten wir die generische Klasse LinkedList <T> : Nehmen wir zwei ihrer Instanzen : LinkedList <Number> und LinkedList <Integer> . Sie sind zwei verschiedene Typen, die nicht miteinander kompatibel sind, selbst wenn Integer Number erweitert ; Situation im Gegensatz zu der, die in Arrays auftritt, wo Integer [] ein Subtyp von Number [] ist . Um dies zu überprüfen, erstellen wir zwei Listen:
LinkedList < Zahl > l1 = neue LinkedList < Zahl > ();
LinkedList < Integer > l2 = new LinkedList < Integer > ();
Und wir betrachten die beiden möglichen Zuordnungen l1 = l2 und l2 = l1; In jedem Fall erhalten Sie eine Fehlermeldung, da LinkedList <Integer> LinkedList <Number> nicht erweitert .
Bisher sind Zuweisungen unmöglich, weil das Substitutionsprinzip verletzt wird : Einer Variablen eines bestimmten Typs kann ein Wert eines beliebigen Untertyps zugewiesen werden; Eine Methode mit einem Argument eines bestimmten Typs kann mit einem Argument eines beliebigen Untertyps aufgerufen werden.
List < Number > numbers = new ArrayList < Number > ();
Zahlen . hinzufügen ( 2 );
Zahlen . füge hinzu ( 3.14d );
Zahlen behaupten . toString (). ist gleich ( "[2, 3.14]" );
Hier gilt das Prinzip zwischen List und ArrayList bzw. zwischen Number und Integer Double . List <Integer> hingegen ist kein Subtyp von List <Number> , da hier wieder das Ersetzungsprinzip verletzt wird , zum Beispiel:
Liste < Ganzzahl > Ganzzahlen = Arrays . alsListe ( 1 , 2 );
Liste < Zahl > zahlen = ganze Zahlen ; // Zahlen nicht kompilieren
. füge hinzu ( 3.14d ); ganze Zahlen behaupten . toString (). ist gleich ( "[1, 2,3.14]" );
Variantenparametrische Typen (Platzhalter)
Es kann keine allgemeine Kompatibilität zwischen parametrischen Typen geben. Wenn Sie nach Kompatibilität suchen, müssen Sie spezielle Fälle und Parametertypen einzelner Methoden berücksichtigen. Daher wird die normale generische List <T>-Typnotation, die zum Erstellen von Objekten verwendet wird, von einer neuen Notation begleitet, die entworfen wurde, um akzeptable Typen als Parameter in einzelnen Methoden auszudrücken.
Wir sprechen daher von Variant Parametric Types, in Java Wildcards genannt .
Da die Notation List <T> den normalen generischen Typ bezeichnet, werden die folgenden Wildcard-Notationen eingeführt :
- Kovariantentyp Liste <? erweitert T> : Erfasst die Eigenschaften von Liste <X> , wobei X T erweitert ; wird verwendet, um Typen anzugeben, die nur gelesen werden können.
- Kontravariantentyp Liste <? super T> : erfasst die Eigenschaften von Liste <X> wobei X um T erweitert wird ; wird verwendet, um Typen anzugeben, die nur geschrieben werden können.
- bivarianter Typ List <?> : erfasst alle List <T> ohne Unterscheidung; wird verwendet, um Typen anzugeben, die weder Lese- noch Schreibvorgänge zulassen.
Definition einer generischen Klasse
Hier ist ein Beispiel für eine generische Klasse
öffentliche Klasse Gen < X , Y > {
privat final X var1 ;
privat final Y var2 ;
öffentlich Gen ( X x , Y y ) {
var1 = x ;
var2 = y ;
}
public X getVar1 () {
return var1 ;
}
öffentlich Y getVar2 () {
return var2 ;
}
öffentlicher String toString () {
return "(" + var1 + "," + var2 + ")" ;
}
}
Diese Klasse allein ist nutzlos, daher muss eine andere Struktur verwendet werden, wie im folgenden Beispiel:
Gen < String , String > example1 = new Gen < String , String > ( "example" , "one" );
Gen < String , Integer > example2 = new Gen < String , Integer > ( "example" , 2 );
System . aus . println ( "erstes Beispiel:" + Beispiel1 );
System . aus . println ( "zweites Beispiel:" + Beispiel2 );