Heim  >  Artikel  >  php教程  >  Generika – Einführung in Generika

Generika – Einführung in Generika

高洛峰
高洛峰Original
2016-12-19 16:02:195148Durchsuche

Einführung in Generika

Nehmen wir ein Beispiel, um zu veranschaulichen, was Generika sind.
Es gibt zwei Klassen wie folgt. Es ist notwendig, Objekte der beiden Klassen zu erstellen und ihre jeweiligen Mitglieder x auszudrucken.
public class StringFoo {
private String x;
public String getX() {
return x
}

public void setX(String x) {
this.x = x;
}

public class DoubleFoo {
public Double getX() {
return x; 🎜> public void setX(Double x) {
this.x = x; Der entsprechende Unterricht ist wirklich langweilig.
Umgestalten Sie daher die beiden oben genannten Klassen in eine Klasse und beachten Sie Folgendes:
In der obigen Klasse ist die Logik der Mitglieder und Methoden dieselbe, die Typen sind jedoch unterschiedlich. Object ist die übergeordnete Klasse aller Klassen. Sie können daher die Verwendung von Object als Mitgliedstyp in Betracht ziehen, damit es universell ist.
public class ObjectFoo {
private Object x;
public Object getX() {
return x
}

public void setX(Object x) {
this.x = x;
}
} Der von

aufgerufene Code lautet wie folgt:
public class ObjectFooDemo {
public static void main(String args[]) {
ObjectFoo strFoo = new ObjectFoo();
strFoo.setX("Hallo Generics!");
ObjectFoo douFoo = new ObjectFoo();
douFoo.setX(new Double("33")) ;
ObjectFoo objFoo = new ObjectFoo();
objFoo.setX(new Object()); Double)dou Foo .getX();
Object obj = objFoo.getX();

System.out.println("strFoo.getX=" + str); ;
System.out.println("strFoo.getX=" + obj); }
}
Das Obige ist, was wir ohne Generika schreiben. Der Code verwendet die Basisklasse Object der obersten Ebene Typdeklaration, übergibt dann den Wert und führt beim Herausnehmen eine erzwungene Typkonvertierung durch.
JDK hat seit 1.5 das Konzept der Generika eingeführt, um solche Probleme elegant zu lösen. Unter Verwendung generischer Technologie lautet der geschriebene Code wie folgt:
public class GenericsFoo {
private T x() {
return x; >
public void setX(T x) {
this.x = x; }
}

Der aufrufende Code lautet wie folgt:
public class GenericsFooDemo {
public static void main(String args[]){
GenericsFoo();
strFoo.setX("Hallo Generics!"); ; douFoo= new GenericsFoo();
douFoo.setX(new Double("33");
Object( )); 🎜> Double d = douFoo.getX();
Object obj = objFoo.getX();
System.out.println("strFoo.getX=" + obj); }
}

Beachten Sie, dass es mehrere offensichtliche Änderungen gibt:
1. Beim Erstellen des Objekts muss der Typ explizit angegeben werden, z. B. GenericsFoo.
2. Wenn das Objekt über die getX-Methode abgerufen wird, ist keine Typkonvertierung erforderlich.
3. Wenn beim Aufruf jeder Methode der Parametertyp nicht mit dem beim Erstellen der Methode angegebenen Typ übereinstimmt, meldet der Compiler einen Fehler.
Warum brauchen wir also Generika? Es gibt zwei Vorteile:
1. Sie können zum Zeitpunkt der Kompilierung überprüfen, ob die gespeicherten Daten korrekt sind. In unserer Entwicklung besteht die Tendenz, Fehler so früh wie möglich zu finden, vorzugsweise während der Kompilierungsphase. Generika erfüllen gerade diese Bedingung.
2. Reduziert die erzwungene Konvertierung, String str = (String)strList.get(0); eine solche Operation ist eine relativ gefährliche Operation, wenn das in der Liste gespeicherte Objekt nicht für String geeignet ist. es wird eine Ausnahme auslösen.
In JDK1.5 unterstützen verschiedene Datentyp-Toolklassen im Paket java.util Generika, die in der Programmierung weit verbreitet sind und beherrscht werden müssen.
Die häufigsten Anwendungen von Generika sind Klassen, Schnittstellen und Methoden, die im Folgenden vorgestellt werden.
3.4.2 Generics werden auf Schnittstellen angewendet:
public interface ValuePair ;
}
Hier repräsentieren A und B beide Typen. In cusp <> können Sie einen Typ oder mehrere Typen verwenden.
3.4.3 Generics werden auf Klassen angewendet:
public class ValuePairImpl b) { first = a; second = b }
public A getA() { return first; }
public String toString() {
Return „(“ + first + „, „ + second + „)“; ,B> implementiert ValuePair .getName() + „ = „ + v.toString();          System.out.println(str); Natürlich kann der Rückgabewert auch ein generischer Typ sein.
3.4.5 Beschränkung der verfügbaren generischen Typen
Die drei oben vorgestellten generischen Anwendungen werden auf Schnittstellen, Klassen und Methoden angewendet. Sie sind ein gängiger Ansatz und unterliegen keinen Einschränkungen hinsichtlich der Typen, die übergeben werden können auf Generika beschränken. In einigen Szenarien möchten wir jedoch die verfügbaren Typen einschränken. Wir hoffen beispielsweise, dass der eingehende Typ von einer bestimmten Klasse erben muss (dh es muss eine Unterklasse, ein Enkel usw. einer bestimmten Klasse sein). In diesem Fall wird die generische Einschränkungssyntax verwendet.
extends: Beschränken Sie generische Typen auf die Nachkommen einer bestimmten Klasse, einschließlich dieses Typs.
Syntax:
Hier ist T ein generischer Typ und das Schlüsselwort „extens“ beschränkt den generischen Typ darauf, ein Nachkomme von parentClass zu sein. parentClass gibt den Typ der übergeordneten Klasse an, die auch eine Schnittstelle sein kann.
In der Java-Sprache können Klassen nur einzeln geerbt werden, und Schnittstellen können mehrfach geerbt werden. Wenn Sie einen bestimmten Typ darauf beschränken möchten, von einer bestimmten Klasse zu erben und mehrere Schnittstellen zu implementieren, lautet die Syntax:
< ;T erweitert parentClass & parentInterface1 & parentInterface2>
Beachten Sie, dass die Klasse vor der Schnittstelle stehen muss.
Ein Beispiel ist wie folgt:
öffentliche Klasse BaseClass {
       int-Wert; 

       public BaseClass(int value) { 
              this.value = value; 
       } 
       
       public int getValue() { 
              Rückgabewert; 
       } 

       public void setValue(int value) { 
              this.value = value; 
       } 
       


public class SubClass erweitert BaseClass{ 
       public SubClass(int value) { 
              super(value*2); 
       } 


öffentliche Klasse GenericBound
       
       public long sum(List tList) { 
              long iValue = 0; 
              for (BaseClass base : tList) { 
                     iValue += base.getValue(); 
              } 
              
              return iValue; 
       } 

       public static void main(String[] args) { 
              GenericBound obj = new 
GenericBound(); 
              
              List list = new LinkedList(); 
              list.add(new SubClass(5)); 
              list.add(new SubClass(6)); 
              
              System.out.println(obj.sum(list)); 
       } 

运行,输出结果为22. 
接着,我们再深入探讨一下。把上面的例子该写如下: 
public class GenericBound
       
       public long sum(List tList) { 
              long iValue = 0; 
              for (BaseClass base : tList) { 
                     iValue += base.getValue(); 
              } 
              
              return iValue; 
       } 

       public static void main(String[] args) { 
              // 注意!!! 
              // 将obj 的类型由GenericBound oder GenericBound ,无法通过编译 
              GenericBound obj = new 
GenericBound(); 
              
List list = new LinkedList();
list.add(new SubClass(6)); (obj.sum(list));
}  
}
Die Anweisung GenericBound(); kann die Kompilierung nicht bestehen schränkt T beim Erstellen dieses Instanztyps auf einen bestimmten Typ ein, und dieser Typ ist ein Nachkomme von BaseClass. Es gibt jedoch viele Nachkommen von BaseClass, z. B. SubClass3 und SubClass4. Wenn Sie für jede einen bestimmten Unterklassentyp schreiben müssen, ist es besser, Object zum Verallgemeinern zu verwenden. Können wir den Typ der übergeordneten Klasse verwenden, um Instanzen verschiedener Unterklassen wie gewöhnliche Klassen einzuführen? Wäre das nicht viel einfacher? Die Antwort lautet „Ja“. Eine bessere Lösung für diese Situation sind „Wildcard-Generika“, die weiter unten ausführlich erläutert werden.
3.4.6 Wildcard-Generika
Die generischen Typen von Java sind gewöhnliche Java-Typen wie java.lang.String und java.io.File. Beispielsweise unterscheiden sich die Typen der folgenden beiden Variablen:
Box();
Obwohl String eine Unterklasse von Object ist, gibt es keine Beziehung zwischen Box – Box ist daher keine Unterklasse oder Unterart von Box :
boxObj = boxStr; // Kompilierung nicht möglich
Daher hoffen wir, dass wir bei der Verwendung von Generika Instanzen verschiedener Unterklassen wie gewöhnliche Klassen einführen können, wodurch die Programmentwicklung vereinfacht wird. Die Generika von Java bieten das Platzhalterzeichen ?, um diese Anforderung zu erfüllen.
Das Codebeispiel lautet wie folgt:
public class WildcardGeneric {
public void print(List lst) {
for (int i = 0; i < lst.size() ; i++) {
               System.out.println(lst.get(i));                                                 new WildcardGeneric();       strList.add("Two");             wg.print(strList) ;                                       25);
intList(30);
                                  Aber in diesem Fall sind die Typen, die die Parameter der WildcardGeneric.print-Methode akzeptieren können, möglicherweise etwas zu weit gefasst für die Entwurfsabsicht des Programmierers. Weil wir vielleicht nur wollen, dass print eine Liste akzeptiert, aber die Elemente in dieser Liste müssen Nachkommen von Number sein. Daher müssen wir Platzhalter einschränken. In diesem Fall können wir die begrenzte Platzhalterform verwenden, um diese Anforderung zu erfüllen. Ändern wir die Druckmethode noch einmal:
public void print(List lst) {
           for (int i = 0; i < lst.size() i++) {                                          .out.print ln( lst.get(i)); Es ist unzulässig, eine generische Typvariable vom Typ Element List (z. B. List) an die Druckmethode zu übergeben.
Außer ? Zusätzlich zur Erweiterung des Platzhalters mit oberer Grenze können wir auch den Platzhalter mit niedrigerer Grenze verwenden, z. B. List.
Lassen Sie uns abschließend die drei Formen generischer Typen mit Platzhaltern zusammenfassen:
GenericTypeWir beherrschen zunächst die grundlegende Verwendung von Generika und gehen dann tiefer in die Themen ein.
Schauen wir uns zuerst einen Code an:
public class GenericsFoo {
private T x; {
return x; 🎜 >
public void setX(T x) {
this.x = x;
}

public static void main(String[] args) {
GenericsFoo gf = new GenericsFoo();
gf.setX("Hello");

GenericsFoo World");                         ! !
String str = gf2.getX(); // Fehler! ! !
gf2.setX(gf2.getX()); // Fehler! ! !
}
}
Beachten Sie, dass die letzten drei Zeilen in der Hauptmethode illegal sind und nicht kompiliert werden können. Es handelt sich ursprünglich um einen generischen Typ von . Nachdem setX() über referenziert wurde, meldet es einen Fehler, wenn ein String übergeben wird, und der Typ des Rückgabewerts von getX() ist nicht String. Noch seltsamer ist, dass die Anweisung gf2.setX(gf2.getX()); selbst wenn Sie den Wert herausnehmen und ihn dann unverändert zurücksetzen, nicht funktioniert. Was ist los?
Um diese Probleme vollständig zu verstehen, müssen wir die internen Implementierungsprinzipien von Generika im JDK verstehen. Schauen wir uns zunächst zwei Beispiele an:
public class GenericClassTest {
public static void main(String[] args) {
Class c1 = new ArrayList().getClass(); c2 = new ArrayList().getClass();
System.out.println(c1 == c2);
} }
}
Nach der Ausführung lautet das Ausgabeergebnis:
wahr
wahr
Dieses Beispiel zeigt, dass die generische ArrayList, die ArrayList und die ArrayList ohne Generics tatsächlich dieselbe Klasse sind. Es ist, als würde man keine Generika verwenden.
Schauen Sie sich das zweite Beispiel an:
class Element {}
class Boxclass Pair

public class GenericClassTest2 { Public Static Void Main (String [] ARGS) {
List & LT; New HashMap & LT; String, Element & GT;       System.out.println(  Class().getTypeParameters())); box.getClass().getTypeParameters()));
System.out.println(
                 p.getClass().getTypeParameters( )));
     } 
}
, das Ausgabeergebnis ist:
[E]
[K, V]
[T]
[KEY, VALUE]
Überprüfen Sie die JDK-Dokumentation The Class.getTypeParameters(. )-Methode gibt ein Array von TypeVariable-Objekten im Array zurück. Jedes TypeVariable-Objekt im Array beschreibt den im Generic deklarierten Typ. Dies scheint zu bedeuten, dass wir den tatsächlichen Typ herausfinden können, wenn ein Generic aus dem TypeVariable-Objekt instanziiert wird. Aus der Ausgabe der Programmausführung können wir jedoch ersehen, dass die von Class.getTypeParameters() zurückgegebenen Serien von TypeVariable-Objekten nur die parametrisierten Typplatzhalter während der generischen Deklaration darstellen und die Typen während der tatsächlichen Instanziierung verworfen werden. Daher ist die Wahrheit über Java-Generika:
Im generischen Code gibt es überhaupt keine Informationen über parametrisierte Typen.
Der Grund dafür ist, dass JDK das Löschen für die interne Implementierung von Generika verwendet. Die spezifische Löschmethode ist wie folgt:
1) ArrayList sind alle gelöscht in ArrayList
2) ArrayList werden alle in ArrayList gelöscht Um den Implementierungsmechanismus des Löschens zu verstehen, gehen wir zurück und analysieren das vorherige Beispiel, um zu sehen, warum es nicht kompiliert werden kann:
public class GenericsFoo
return x;
}

public void setX(T x) {
this.x = x;
}

public static void main(String[ ] args) {
GenericsFoo();
gf.setX("Hallo") ; >            gf2.setX("World"); ! !
String str = gf2.getX(); // Fehler! ! !
gf2.setX(gf2.getX()); // Fehler! ! !
}
}
Aufgrund des Löschmechanismus werden GenericsFoo alle in GenericsFoo gelöscht. Nachdem der Typ verloren gegangen ist, wird die entsprechende Methodendeklaration zu:
public Object getX( ; Für alle Set-Methode einer Typklasse, die einen Wert festlegt, wird der Parametertyp zu Null. Kein Typ kann in einen Nulltyp konvertiert werden, daher können nicht alle Set-Methoden aufgerufen werden.
Dies stellt ein interessantes Phänomen dar: Für den generischen Typ von können Sie ihn nur abrufen, aber nicht festlegen.
Das Obige erklärt den Löschmechanismus von Java-Generika, der zum Verlust einiger nützlicher Typinformationen führt. Wir können jedoch einige Tricks verwenden, damit der Compiler die Typinformationen rekonstruiert, sodass die Set-Methode normal aufgerufen werden kann. Siehe folgenden Code:
public void setGeneric(GenericsFoo foo) {
setGenericHelper(foo);
}

private foo ) {
foo.setX(foo.getX());
}
setGenericHelper() ist eine generische Methode, die zusätzliche Typparameter einführt (in spitzen Klammern vor dem Rückgabetyp). werden verwendet, um Typbeschränkungen zwischen Parametern und/oder Rückgabewerten von Methoden auszudrücken. setGenericHelper () Mit dieser Deklarationsmethode kann der Compiler (über die Typschnittstelle) die Typparameter des GenericsFoo-Generikums benennen. Aber ein Typ kann eine Elternklasse und eine Großelternklasse haben und auch mehrere Schnittstellen implementieren. In welchen Typ wird der Compiler also konvertieren?
Wir verwenden den folgenden Code, um es zu überprüfen:
public class GenericClassTest3 {
public static String getType(T arg) {
return arg.getClass().getName(); > }

public static void main(String[] args) {
Integer i = new Integer(5);
System.out.println(GenericClassTest3.get Type(i)); 🎜>} }
Nachdem das Programm ausgeführt wurde, lautet das Ausgabeergebnis:
java.lang.integer
Daher kann der Compiler T als Ganzzahl, Zahl, serialisierbar oder Objekt ableiten, aber Es wählt Integer als den spezifischsten Typ, der die Einschränkung erfüllt.
Darüber hinaus können wir aufgrund des Löschmechanismus von Generika den neuen Operator nicht direkt für generische Typen verwenden, wie zum Beispiel:
public class GenericNew                  T obj = new T(); ! !
              return obj; Die Kompilierung schlägt also fehl.
In einigen Fällen benötigen wir jedoch immer noch eine dynamische Instanziierung generischer Typen. Für die Erstellung eines einzelnen Objekts und eines Arrays lautet das Codebeispiel wie folgt:
public class GenericNew Object obj = cls.newInstance();                                                                       } Catch(Exception e) {
                                                                                                                                       obj;
} Catch (Ausnahme e) {
                       return null;                              Im obigen Code realisiert die Methode create die dynamische Erstellung einer Instanz eines generischen Typs Die Methode createArray realisiert die dynamische Erstellung eines Instanzarrays vom generischen Typ.




Weitere Artikel zum Thema Generika – Einführung in Generika finden Sie auf der chinesischen PHP-Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn