Heim  >  Artikel  >  Java  >  Detaillierte Erläuterung der Java-Generika

Detaillierte Erläuterung der Java-Generika

高洛峰
高洛峰Original
2016-12-19 14:58:461227Durchsuche
  1. Übersicht
    Vor der Einführung von Generika wurden Java-Typen in primitive Typen und komplexe Typen unterteilt. Komplexe Typen wurden in Arrays und Klassen unterteilt. Nach der Einführung von Generika kann ein komplexer Typ
    in weitere Typen unterteilt werden.
    Zum Beispiel ist der ursprüngliche Typ List jetzt in List, List unterteilt.
    Beachten Sie, dass List jetzt zwei verschiedene Typen sind.
    Es besteht keine Vererbungsbeziehung zwischen ihnen, auch wenn String Object erbt. Der folgende Code ist illegal:
    List ls = new ArrayList();
    List Der Compiler ermöglicht das Hinzufügen beliebiger Objekte (z. B. Integer) zu lo, aber dieses Objekt ist
    List, wodurch die Integrität des Datentyps zerstört wird.
    Wenn Sie möchten, dass die Methoden in der Klasse mehrere Datentypen unterstützen, müssen Sie vor der Einführung von Generika die Methoden überladen. Nach der Einführung von Generika kann dieses Problem gelöst werden (Polymorphismus) und Sie können mit der Definition fortfahren Beziehungen zwischen mehreren Parametern und Rückgabewerten.
    Zum Beispiel:
    public void write(Integer i, Integer[] ia);
    public void write(Double d, Double[] da); Die generische Version von
    ist
    public void write(T t, T[] ta);

    2. Definieren und verwenden Sie den Namensstil der Typparameter:
    Es wird empfohlen, prägnante Namen zu verwenden die Namen formaler Typparameter (möglichst ein einzelnes Zeichen). Vermeiden Sie am besten Kleinbuchstaben, da dies die Unterscheidung von anderen gewöhnlichen formalen
    -Parametern erleichtert.
    Verwenden Sie T zur Darstellung des Typs, wenn es keinen spezifischeren Typ zur Unterscheidung gibt. Dies wird häufig bei generischen Methoden beobachtet. Wenn mehrere Typparameter vorhanden sind,
    verwenden wir möglicherweise einen Buchstaben in der Nähe von T im Alphabet, z. B. S.
    Wenn eine generische Funktion innerhalb einer generischen Klasse erscheint, ist es am besten, die Verwendung desselben Namens im Typparameter der Methode und im Typparameter der Klasse zu vermeiden, um Verwirrung zu vermeiden
    . Das Gleiche gilt für innere Klassen.

    2.1 Definieren Sie eine Klasse mit Typparametern
    Geben Sie beim Definieren einer Klasse mit Typparametern im Feld <> direkt nach dem Klassennamen den Namen eines oder mehrerer Typparameter an Beschränken Sie den
    -Wertebereich von Typparametern und trennen Sie mehrere Typparameter durch ein Vorzeichen.
    Nach der Definition von Typparametern können Sie Typparameter fast überall in der Klasse nach der Definitionsposition verwenden (außer statische Blöcke, statische Eigenschaften, statische Methoden),
    genau wie bei der Verwendung gewöhnlicher Typen.
    Beachten Sie, dass von der übergeordneten Klasse definierte Typparameter nicht von der Unterklasse geerbt werden können.
    öffentliche Klasse TestClassDefine Geben Sie im <> unmittelbar nach der Änderung des sichtbaren Bereichs den Namen eines oder mehrerer Typparameter an per Zeichen.
    Nachdem Sie die Typparameter definiert haben, können Sie die Typparameter an einer beliebigen Stelle in der Methode nach der Definitionsposition verwenden, genau wie bei der Verwendung gewöhnlicher Typen.
    Zum Beispiel:
    public Der Hauptzweck von Ride besteht darin, die Beziehung zwischen mehreren Parametern und Rückgabewerten auszudrücken. Beispielsweise ist in der
    -Vererbungsbeziehung zwischen T und S in diesem Beispiel der Typ des Rückgabewerts derselbe wie der Wert des ersten Typparameters.
    Wenn Sie nur Polymorphismus erreichen möchten, verwenden Sie bitte zuerst Platzhalter. Platzhalterinhalte finden Sie im folgenden Abschnitt.
    public void testGenericMethodDefine2(List s){
    ...
    }
    sollte in
    public void testGenericMethodDefine2(List s){ geändert werden
    ...
    }

    3. Typparameterzuweisung
    Beim Zuweisen von Werten zu Typparametern einer Klasse oder Methode müssen alle Typparameter zugewiesen werden. Andernfalls erhalten Sie einen Kompilierungsfehler.

    3.1 Zuweisen von Typparametern zu einer Klasse mit Typparametern
    Es gibt zwei Möglichkeiten, einer Klasse Typparameter mit Typparametern zuzuweisen
    Erstens, wenn eine Klassenvariable deklariert oder instanziiert wird. Beispiel:
    List list;
    list = new ArrayList;
    wenn die zweite geerbte Klasse oder die Schnittstelle implementiert. Beispielsweise implementiert
    die öffentliche Klasse MyList List 3.2 Zuweisen von Werten zu Methoden mit Typparametern
    , Kompilieren Der Compiler weist Typparametern automatisch Werte zu. Wenn die Zuweisung nicht erfolgreich ist, wird ein Kompilierungsfehler gemeldet. Zum Beispiel
    public T testGenericMethodDefine3(T t, List list){
    ...
    }
    public testGenericMethodDefine4(List list2 ){
    ...
    }

    Zahl n = null;
    Ganzzahl i = null;
    Objekt o = null;
    testGenericMethodDefine(n, i);/ /Zu diesem Zeitpunkt ist T eine Zahl, S ist eine Ganzzahl
    testGenericMethodDefine(o, i);//T ist ein Objekt, S ist eine Ganzzahl

    List list1 = null;
    testGenericMethodDefine3(i , list1)//Zu diesem Zeitpunkt ist T Number

    List list2 = null;
    testGenericMethodDefine4(list1, list2)//Kompilierungsfehler

    3.3 Wildcard
    in Die beiden oben genannten In diesem Abschnitt werden Typparametern bestimmte Werte zugewiesen. Darüber hinaus können Typparametern auch undefinierte Werte zugewiesen werden. Zum Beispiel:
    List unlimited Number> unlimitedNumberList;
    List von unbekanntem Typ Die Containerklasse kann nur die darin enthaltenen Elemente lesen, aber keine Elemente hinzufügen.
    Da ihr Typ unbekannt ist, kann der Compiler nicht erkennen, ob der Typ des hinzugefügten Elements mit dem Typ des Containers kompatibel ist. Die einzige Ausnahme ist NULL
    4. Array-Spezifikationstyp
    Sie können eine Klasse mit generischen Parameterwerten verwenden, um ein Array zu deklarieren, aber Sie können kein Array erstellen
    List[] iListArray;
    new ArrayList< Integer>[10];//Kompilierungszeitfehler

    5. Implementierungsprinzip

    5.1. Die generische Java-Kompilierungszeit-Technologie enthält keine generischen Informationen zur Laufzeit von Typparametern.
    Generika werden durch einen Front-End-Prozess namens Erasure durch den Java-Compiler implementiert. Man kann es sich (im Grunde) als eine Quelle-zu-Quelle-Transformation vorstellen, die die generische Version in die nicht-generische Version umwandelt.
    Grundsätzlich werden beim Löschen alle generischen Typinformationen entfernt. Alle Typinformationen zwischen spitzen Klammern werden verworfen, sodass beispielsweise ein
    List-Typ in List konvertiert wird. Alle Verweise auf Typvariablen werden durch die Obergrenze der Typvariablen (normalerweise Objekt) ersetzt. Außerdem
    wird immer dann, wenn der resultierende Codetyp falsch ist, eine Konvertierung in den entsprechenden Typ eingefügt.
                                                                                                                                                                                . Das bedeutet, dass sie keinen zusätzlichen Zeit- oder Platzaufwand verursachen, was sehr praktisch ist. Leider bedeutet dies auch
    , dass Sie sich bei der Typkonvertierung nicht auf sie verlassen können.

    5.2. Eine generische Klasse wird von allen ihren Aufrufen gemeinsam genutzt.
    Was wird durch den folgenden Code ausgegeben?
    List l1 = new ArrayList();
    List l2 = new ArrayList();
    System.out.println(l1.getClass() == l2.getClass());
    Vielleicht sagen Sie „falsch“, aber Sie liegen falsch. Es wird wahr ausgegeben. Weil alle Instanzen einer generischen Klasse zur Laufzeit dieselbe Laufzeitklasse haben, unabhängig von ihren tatsächlichen Typparametern.
    Tatsächlich wird ein Generikum als Generikum bezeichnet, weil es für alle möglichen Typparameter das gleiche Verhalten aufweist. Dieselbe Klasse kann für viele verschiedene
    Typen verwendet werden. Dadurch werden auch die statischen Variablen und Methoden der Klasse von allen Instanzen gemeinsam genutzt. Aus diesem Grund ist es illegal, Typparameter (Typparameter gehören zu bestimmten Instanzen) in statischen Methoden oder statischem Initialisierungscode
    oder beim Deklarieren und Initialisieren statischer Variablen zu verwenden.

    5.3. Casting und Instanz von
    Eine weitere Implikation, dass eine generische Klasse von allen ihren Instanzen gemeinsam genutzt wird, ist, dass es keinen Sinn macht zu prüfen, ob eine Instanz ein bestimmter Typ einer generischen Klasse ist.
    Collection cs = new ArrayList();
    if (cs caseof Collection) { ...} // Illegal
    Ähnlich ist die folgende Typkonvertierung
    Collection) cs;
    Erhalten Sie eine ungeprüfte Warnung, da die Laufzeitumgebung eine solche Prüfung nicht für Sie durchführt.

    6. Generische Verarbeitung der Klasse
    Nach Java 5 wird die Klasse generisch.
    Eine der Änderungen in JDK1.5 besteht darin, dass die Klasse java.lang.Class generisch ist. Dies ist ein interessantes Beispiel für die Erweiterung von Generika über Containerklassen hinaus.
    Nun hat die Klasse einen Typparameter T. Sie fragen sich vielleicht, wofür T steht? Es stellt den Typ dar, der durch das Class-Objekt dargestellt wird. Beispielsweise stellt der Typ
    String.class Class dar und Serializable.class stellt Class dar.
    Dies kann verwendet werden, um die Typsicherheit Ihres reflektierten Codes zu verbessern.
    Da insbesondere die newInstance()-Methode der Klasse jetzt ein T zurückgibt, können Sie beim Erstellen von Objekten mithilfe von Reflektion einen präziseren Typ erhalten.
    Angenommen, Sie möchten beispielsweise eine Toolmethode schreiben, um eine Datenbankabfrage anhand einer SQL-Anweisung durchzuführen und eine Sammlung von Objekten in der Datenbank zurückzugeben, die die Abfragebedingungen erfüllen
    .
    Eine Methode besteht darin, ein Factory-Objekt explizit zu übergeben, wie der folgende Code:
    interface Factory public T[] make();
    }
    public Collection select(Factory Factory, String-Anweisung) {
    Collection result = new ArrayList();
    /* SQL-Abfrage mit JDBC ausführen */
    für ( int i=0; i<10; i++ ) { /* über JDBC-Ergebnisse iterieren */
              T item = Factory.make();
                                                                              /
             result.add( item); {
                                                                                                                                                                                                ;EmpInfo> { ...
    public EmpInfo make() { return new EmpInfo(); }
    }
    Dann rufen Sie auf:
    select(getMyEmpInfoFactory(), "selection string" );
    Der Nachteil dieser Lösung besteht darin, dass sie eines von zwei Dingen erfordert:
    eine ausführliche anonyme Factory-Klasse an der Aufrufstelle oder die Deklaration einer Factory-Klasse für jeden zu verwendenden Typ und die Übergabe ihres Objekts an den Aufruf Seite
    Das ist unnatürlich.
    Die Verwendung von Klassentyp-Parameterwerten ist sehr natürlich und kann durch Reflexion verwendet werden. Code ohne Generika kann sein:
    Collection emps = sqlUtility.select(EmpInfo.class, ”select * from emps”); ...
    public static Collection select(Class c, String sqlStatement) {
    Collection result = new ArrayList()                                                                                                                                                                                                                                     Ergebnis .add(item); Der genaue Typ der Sammlung, den wir wollen. Da die Klasse nun generisch ist, können wir schreiben:
    Collection emps=sqlUtility.select(EmpInfo.class, ”select * from emps”); ;select(Classc, String sqlStatement) {
    Collection result = new ArrayList();
    /* SQL-Abfrage mit jdbc ausführen */
    für ( /* iterate über JDBC-Ergebnisse */ ) {
    T item = c.newInstance();
    /* Reflexion verwenden und alle Felder des Elements aus SQL-Ergebnissen festlegen */
    result.add(item);
    }
    return result;
    }
    um die gewünschte Sammlung typsicher zu erhalten.
    Diese Technik ist ein sehr nützlicher Trick, der in neuen APIs, die Anmerkungen verarbeiten, zu einer weit verbreiteten Redewendung geworden ist.

    7. Kompatibilität von altem und neuem Code

    7.1 Um die Codekompatibilität zu gewährleisten, wird die Typsicherheit durch Sie selbst gewährleistet
    Liste l = new ArrayList();
    List l = new ArrayList();

    7.2 Verwenden Sie kovariante Rückgabewerte mit Vorsicht.
    Ändern Sie beispielsweise den Code
    public class Foo {
    public Foo create(){
    } return new Foo();
    }
    }

    public Klasse Bar erweitert Foo {
    public Foo create(){
                                                                                                                                 use   using kovarianten Rückgabewertstil               use ’ s ’ s ‐ ‐ ‐ ‐ ‐‐ ‐ return new Bar(); pblic bar create () {
    Return New Bar ( );
    }
    }
    Seien Sie vorsichtig mit Ihrem Kunden Ihrer Bibliothek.




    Weitere Artikel zu Java-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