close

Generika Java

Zur Navigation springen Zur Suche springen

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

Notizen