1. Warum - der Grund für die Einführung generischer Mechanismen
Wenn wir ein String -Array implementieren und die Größe dynamisch verändern müssen, werden wir alle denken, dass ArrayList zu aggregierten String -Objekten verwendet wird. Nach einer Weile möchten wir jedoch eine Reihe von Datumsobjekten implementieren, deren Größe geändert werden kann. Zu diesem Zeitpunkt hoffen wir auf jeden Fall, die ArrayList -Implementierung für String -Objekte wiederverwenden zu können, die ich zuvor geschrieben habe.
Vor Java 5 ist die Implementierung von ArrayList ungefähr wie folgt:
public class ArrayList {public Object get (int i) {...} public void add (Objekt o) {...} ... privates Objekt [] elementData;}Aus dem obigen Code können wir feststellen, dass die Funktion hinzufügen, mit der Elemente zur ARRAYList hinzugefügt werden, einen Parameter vom Objekttyp empfängt. Die GET-Methode, die das angegebene Element aus der ArrayList erhalten, gibt auch ein Objekttyp-Objekt zurück. Das Objekt -Objekt -Array -ElementData speichert das Objekt in der ArrayList. Das heißt, egal welche Art von Typ Sie in die ArrayList einfügen, es ist ein Objektobjekt darin.
Eine generische Implementierung auf der Grundlage der Erbschaft bringt zwei Probleme mit sich: Die erste Frage bezieht sich auf die GET -Methode. Jedes Mal, wenn wir die GET -Methode aufrufen, werden wir ein Objektobjekt zurückgeben, und jedes Mal, wenn wir den Typ auf den benötigten Typ werfen müssen, der sehr problematisch erscheint. Die zweite Frage bezieht sich auf die Methode hinzufügen. Wenn wir der ArrayList, die das String -Objekt aggregiert, ein Dateiobjekt hinzufügen, generiert der Compiler keine Fehleranforderungen, was nicht das ist, was wir wollen.
Ausgehend von Java 5 kann ArrayList bei der Verwendung eines Typ -Parameters (Typparameter) verwendet werden. Dieser Typparameter wird verwendet, um den Elementtyp in ArrayList anzuzeigen. Die Einführung der Typparameter löst die beiden oben genannten Probleme, wie im folgenden Code gezeigt:
ArrayList <string> S = New ArrayList <String> (); S.Add ("ABC"); String S = S.Get (0); // keine Notwendigkeit, S.Add (123) zu besetzen; // Kompilierungsfehler können Sie nur String -Objekte hinzufügen ...Im obigen Code wird nach dem Compiler die Parameterzeichenfolge des Typs der ArrayList "kennt". Er wird das Gießen abschließen und die Überprüfung für uns eingeben.
2. Generische Klassen
Die sogenannte generische Klasse ist eine Klasse mit einem oder mehreren Typparametern. Zum Beispiel:
Public Class Pair <t, u> {privat t zuerst; privat u zweiter; public pair (t zuerst, u zweiter) {this.First = First; this.second = Second; } public t getFirst () {return zuerst; } public u getSecond () {return Second; } public void setFirst (t NewValue) {first = newValue; }}Im obigen Code können wir sehen, dass die Typparameter des generischen Klassenpaars T und U sind und nach dem Klassennamen in Winkelklammern platziert werden. Hier bedeutet T den ersten Typ vom Typ, der Typ darstellt. Am häufigsten verwendeten werden E (Element), K (Schlüssel), V (Wert) usw. Natürlich auch vollkommen in Ordnung, diese Buchstaben nicht zu verwenden, um sich auf Typparameter zu verweisen.
Wenn wir eine generische Klasse instanziieren, müssen wir den Typ -Parameter nur durch einen bestimmten Typ ersetzen, z.
Pair <String, Integer> pair = New Pair <String, Integer> ();
3. Generische Methoden
Die sogenannte generische Methode ist eine Methode mit Typparametern. Es kann in einer generischen Klasse oder einer normalen Klasse definiert werden. Zum Beispiel:
public class arrayalg {public static <t> t getMiddle (t [] a) {return a [A.Length / 2]; }}Die GetMiddle -Methode im obigen Code ist eine generische Methode, und das definierte Format ist, dass die Typvariable nach dem Modifikator und vor dem Rückgabetyp platziert wird. Wir können sehen, dass die oben genannten generischen Methoden für verschiedene Arten von Arrays erforderlich sind. Wenn die Arten dieser Arrays begrenzt sind, obwohl sie auch mit Überlastung implementiert werden können, ist die Codierungseffizienz viel niedriger. Der Beispielcode zum Aufrufen der obigen generischen Methode lautet wie folgt:
String [] strings = {"aa", "bb", "cc"};
String Middle = arrayalg.getMiddle (Namen);
4. Begrenzung der Typvariablen
In einigen Fällen möchten generische Klassen oder generische Methoden ihre Typparameter weiter einschränken. Wenn wir beispielsweise Typparameter definieren möchten, die nur Unterklassen einer bestimmten Klasse oder nur Klassen sein können, die eine bestimmte Schnittstelle implementieren. Die relevante Syntax ist wie folgt:
<T erweitert BoundingType> (BockeningType ist eine Klasse oder Schnittstelle). Es kann mehr als 1 BedingType geben. Verwenden Sie einfach "&", um eine Verbindung herzustellen.
5. Verstehen Sie die Implementierung von Generika
Aus der Sicht virtueller Maschinen gibt es kein Konzept von "Generika". Das oben definierte generische Klassenpaar sieht beispielsweise in der virtuellen Maschine so aus (dh nach der Zusammenstellung in Bytecode):
Public Class Pair {privates Objekt zuerst; privates Objekt an zweiter Stelle; öffentliches Paar (Objekt zuerst, Objekt zweitens) {this.First = First; this.second = Second; } öffentliches Objekt getFirst () {return zuerst; } public Object GetEcond () {Return Second; } public void setFirst (Objekt newValue) {first = newValue; } public void setSecond (Objekt newValue) {Second = newValue; }}Die obige Klasse wird durch Typenlöschungen erhalten und ist der Rohtyp, der der generischen Klasse der Paare entspricht. Das Löschen des Typs bedeutet, alle Typparameter durch Begrenzungstyp zu ersetzen (ersetzen Sie es durch Objekt, wenn keine Einschränkungen hinzugefügt werden).
Wir können einfach überprüfen, ob Pair.java "javap -c -S -Paar" eingeben, um zu erhalten:
Die Linie mit "Deskriptor" in der obigen Abbildung ist die Signatur der entsprechenden Methode. Zum Beispiel können wir aus der vierten Zeile sehen, dass die beiden formalen Parameter des Paarkonstruktors nach dem Typenlöscher zu Objekt geworden sind.
Da das generische Klassenpaar in der virtuellen Maschine zu seinem Rohtyp wird, gibt die GetFirst -Methode ein Objektobjekt zurück, und aus der Perspektive des Compilers gibt diese Methode ein Objekt des Typ -Parameters zurück, der angegeben wird, wenn wir die Klasse instanziieren. Tatsächlich ist es der Compiler, der uns hilft, die Casting -Arbeiten zu erledigen. Mit anderen Worten, der Compiler wandelt den Aufruf in die GetFirst -Methode in der generischen Klasse in zwei virtuelle Maschinenanweisungen um:
Der erste ist ein Aufruf der RAW -Typ -Methode getFirst, die ein Objektobjekt zurückgibt. Die zweite Anweisung läuft das zurückgegebene Objekt Objekt in den angegebenen Parametertyp des Typs.
Das Typ -Löschen tritt auch in generischen Methoden auf, wie beispielsweise die folgenden generischen Methoden:
Public static <t erweitert vergleichbar> T min (t [] a)
Nach der Zusammenstellung wird es nach dem Typenlösch wie dieses:
Public static vergleichbar min (vergleichbar [] a)
Das Auslöschen von Methoden kann einige Probleme verursachen. Berücksichtigen Sie den folgenden Code:
Klasse DateInterval erweitert Paar <Datum, Datum> {public void setSecond (Datum Second) {if (Second.comPareto (getFirst ())> = 0) {Super.SetSeScond (Second); }} ...}Nachdem der obige Code nach Typ gelöscht wurde, wird er:
Klasse DateInterval erweitert das Paar {public void setSecond (Datum Sekunde) {...} ...}In der DateInterval -Klasse gibt es auch eine SetSecond -Methode, die von der Paarklasse (nach dem Löschen des Typs) geerbt wurde, wie folgt:
öffentliche void setSecond (Objekt Sekunde)
Jetzt können wir feststellen, dass diese Methode unterschiedliche Methodensignaturen (unterschiedliche formale Parameter) aus der SETSECOND -Methode hat, die nach Datumsinterval überschrieben wurde. Daher sind es zwei unterschiedliche Methoden. Diese beiden Methoden sollten jedoch keine unterschiedlichen Methoden sein (weil sie überschrieben wird). Betrachten Sie den folgenden Code:
DateInterval -Intervall = neues DatumInterval (...); Paar <Datum, Datum> Paar = Intervall; Datum adate = neues Datum (...); pair.setsecond (adate);
Aus dem obigen Code können wir sehen, dass sich das Paar tatsächlich auf das Datumsinterval -Objekt bezieht, sodass die SetSecond -Methode des Datumsintervals aufgerufen werden sollte. Das Problem ist hier, dass Typen, die Konflikte mit Polymorphismus löschen.
Lassen Sie uns herausfinden, warum dieses Problem auftritt: Das Paar wurde zuvor als Typpaar <Datum, Datum> deklariert, und diese Klasse scheint in der virtuellen Maschine nur eine "setSecond (objekt)" -Methode zu haben. Beim Ausführen stellt die virtuelle Maschine fest, dass sich das Paar tatsächlich auf das Datumsinterval -Objekt bezieht, und ruft die "setSecond (objekt)" des Datumsintervals auf, aber es gibt nur die Methode "setSecond (Datum)" in der DateInterval -Klasse.
Die Lösung für dieses Problem besteht darin, durch den Compiler eine Brückenmethode in DateInterval zu generieren:
public void setSecond (Objekt Sekunde) {setSecond ((Datum) Sekunde);}6. Dinge zu beachten
(1) Die Typparameter können nicht mit Basistypen instanziiert werden
Das heißt, die folgende Erklärung ist illegal:
Paar <int, int> pair = new pair <int, int> ();
Wir können jedoch stattdessen den entsprechenden Verpackungstyp verwenden.
(2) können generische Klasseninstanzen nicht werfen oder erfassen können
Die generische Klassenerweiterung ist illegal, sodass generische Klasseninstanzen nicht geworfen oder erfasst werden können. Es ist jedoch legal, Typparameter in Ausnahmeerklärungen zu verwenden:
public static <t erweitert Throwable> void Dowork (t t) t {try {...} catch (throwable realcause) {t.initcause (RealCause); t werfen; }}(3) Das parametrisierte Array ist illegal
In Java kann ein Objekt [] Array die übergeordnete Klasse eines jeden Arrays sein (da jedes Array nach oben in ein Array der übergeordneten Klasse umgewandelt werden kann, das den Elementtyp angibt, wenn es definiert ist). Betrachten Sie den folgenden Code:
String [] strs = new String [10]; Objekt [] objs = strs; obj [0] = neues Datum (...);
Im obigen Code weisen wir dem Array -Element einem Objekt, das den übergeordneten Klasse (Objekt) erfüllt, zu, im Gegensatz zum ursprünglichen Typ (Paar) kann es zur Kompilierungszeit übergeben, und eine Ausnahme von ArrayStoreException wird zur Laufzeit geworfen.
Angenommen, Java ermöglicht es uns, ein generisches Array durch die folgende Erklärung zu deklarieren und zu initialisieren:
Paar <String, String> [] pairs = new pair <String, String> [10];
Nachdem die virtuelle Maschine das Typenlöschen ausführt, werden Paare tatsächlich zu Paaren [] Arrays, und wir können es nach oben in ein Objekt [] -Array umwandeln. Wenn wir zu diesem Zeitpunkt Paar <Datum, Datum> Objekte dazu hinzufügen, können wir Kompilierungszeitprüfungen und Laufzeitprüfungen bestehen. Unsere ursprüngliche Absicht ist es, dieses Arrayspeicherpaar <String, String> Objekte zuzulassen, was zu schwierig ist, Fehler zu finden. Daher erlaubt uns Java nicht, ein generisches Array durch das obige Anweisungsformular zu deklarieren und zu initialisieren.
Ein generisches Array kann unter Verwendung der folgenden Anweisung deklariert und initialisiert werden:
Pair <String, String> [] pairs = (pair <string, string> []) neues Paar [10];
(4) Typvariable kann nicht instanziiert werden
Typvariablen können nicht in Formularen wie "New t (...)", "New t [...]", "T.Class" verwendet werden. Der Grund, warum Java es uns verbietet, dies zu tun, ist einfach. Da es Typenlöschungen gibt, werden Aussagen wie "neues t (...)" "neues Objekt (...)", was normalerweise nicht das ist, was wir meinen. Wir können den Anruf durch "New t [...]" durch die folgende Erklärung ersetzen:
Arrays = (t []) neues Objekt [n];
(5) Typvariablen können nicht im statischen Kontext generischer Klassen verwendet werden
Beachten Sie, dass wir hier generische Klassen betonen. Da statische generische Methoden in normalen Klassen definiert werden können, wie die Getmiddle -Methode in der oben genannten Arrayalg -Klasse. Bitte betrachten Sie aus Gründen einer solchen Regel den folgenden Code:
public class people <t> {public static t name; public static t GetName () {...}}Wir wissen, dass es gleichzeitig mehr als eine Personen -Klasseninstanz im Gedächtnis gibt. Angenommen, es gibt jetzt ein People <String> -Objekt und peoper <Ganzzahl> -Objekt, und die statischen Variablen und statischen Methoden der Klasse werden von allen Klasseninstanzen geteilt. Die Frage ist also, ist der Namenszeichenfolge Typ oder Ganzzahl? Aus diesem Grund sind in Java nicht zulässig, dass Typvariablen in statischen Kontexten generischer Klassen verwendet werden.
7. Typ Wildcard
Vor der Einführung der Typ -Wildcard stellen Sie zunächst zwei Punkte ein:
.
(2) Es gibt eine "is-a" -Behunde zwischen Paar <T, T> und seinem ursprünglichen Typpaar. Pair <t, t> kann in jedem Fall in Paartyp konvertiert werden.
Betrachten Sie nun diese Methode:
public static void printName (Paar <people, people> p) {people p1 = p.getFirst (); System.out.println (p1.getName ()); // Angenommen, die People -Klasse definiert die GetName -Instanzmethode}In der obigen Methode möchten wir in der Lage sein, die Parameter von Paaren <Student, Student> und People <People, People> gleichzeitig zu übergeben, aber es gibt keine "is-a" -Beziehung zwischen den beiden. In diesem Fall bietet Java uns eine Lösung: Verwenden Sie Paar <? Erweitert Menschen> als die Art des formalen Parameters. Das heißt, Paar <Student, Student> und Pair <People, People> können beide als Unterklassen von Paaren angesehen werden <? verlängert Menschen>.
Der Code, der wie "<?? BoundingType>" aussieht, wird als Subtyp -Begrenzung von Wildcard -Zeichen bezeichnet. Entsprechend ist das Format wie folgt: <? super boundingType>.
Betrachten wir nun den folgenden Code:
Pair <Student> student = New Pair <Student> (student1, student2); pair <? erweitert Menschen> Wildchards = Schüler; Wildchards.Setfirst (People1);
Die dritte Zeile des obigen Codes meldet einen Fehler, da Wildchards ein Paar <? Erweitert Menschen> Objekt, und seine methodische Methode und die GetFirst -Methode sind wie folgt:
void setfirst (? Erweitert Menschen)? erweitert die Menschen GetFirst ()
Für die setFirst -Methode weiß der Compiler nicht, welche Art von formalen Parametern (nur bekannt als Unterklasse von Menschen). Wenn wir versuchen, ein Personenobjekt zu übergeben, kann der Compiler nicht bestimmen, ob die Personen und formalen Parameter "IS-A" sind. Wenn Sie also die SetFirst-Methode aufrufen, meldet dies einen Fehler. Es ist legal, Wildchards 'GetFirst -Methode zu bezeichnen, weil wir wissen, dass sie eine People -Unterklasse zurückgeben und die Unterklasse der Menschen "immer ein Volk" ist. (Sie können immer Unterklassenobjekte in übergeordnete Objekte konvertieren)
Im Falle einer Wildcard -Supertyp -Begrenzung ist es illegal, die Getter -Methode zu bezeichnen, während es legal ist, die Setter -Methode zu bezeichnen.
Zusätzlich zu den Einschränkungen der Subtypen und der Supertyp -Grenzen gibt es auch eine Wildcard namens Infinite Wildcard, die wie folgt aussieht: <?>. Wann werden wir dieses Ding verwenden? Betrachten Sie dieses Szenario. Wenn wir eine Methode aufrufen, werden wir eine GetPairs -Methode zurückgeben, die einen Satz von Paaren <T, t> Objekte zurückgibt. Unter ihnen sind Paar <Student, Schüler> und Paar <Lehrer, Lehrer> Objekte. (Es gibt keine Vererbungsbeziehung zwischen der Schülerklasse und der Lehrerklasse.) In diesem Fall können in diesem Fall sowohl die Subtyp -Beschränkung als auch die Supertyp -Beschränkung nicht verwendet werden. Zu diesem Zeitpunkt können wir diese Anweisung verwenden, um sie zu lösen:
Paar <?> [] Pairs = getPairs (...);