Frage Was ist cool an Generika, warum sie verwenden?


Ich dachte, ich würde diesen Softball jedem anbieten, der ihn gerne aus dem Park schlagen würde. Was sind Generika, was sind die Vorteile von Generika, warum, wo, wie soll ich sie verwenden? Bitte, halte es ziemlich einfach. Vielen Dank.


75
2017-09-16 21:57


Ursprung


Antworten:


  • Ermöglicht das Schreiben von code- / use-Bibliotheksmethoden, die typsicher sind, d. H. Eine List <string> ist garantiert eine Liste von Strings.
  • Als Ergebnis der Verwendung von Generics kann der Compiler Codeüberprüfungen zur Typprüfung zur Kompilierungszeit durchführen, d. H., Versuchen Sie, ein int in diese Liste von Strings zu setzen? Die Verwendung einer ArrayList würde dazu führen, dass es sich um einen weniger transparenten Laufzeitfehler handelt.
  • Schneller als die Verwendung von Objekten, da entweder Boxen / Unboxing vermieden wird (wo .net konvertiert werden muss) Werttypen zu Referenztypen oder umgekehrt) oder das Umwandeln von Objekten in den erforderlichen Referenztyp.
  • Ermöglicht das Schreiben von Code, der für viele Typen mit demselben zugrunde liegenden Verhalten anwendbar ist, d. H. Ein Dictionary <string, int> verwendet denselben zugrunde liegenden Code wie ein Dictionary <DateTime, double>; Mit Generics musste das Framework-Team nur einen Code schreiben, um beide Ergebnisse mit den oben genannten Vorteilen zu erreichen.

112
2017-09-16 22:04



Ich hasse es wirklich, mich selbst zu wiederholen. Ich hasse es, öfter dasselbe zu tippen als nötig. Ich mag es nicht, Dinge mehrmals mit kleinen Unterschieden zu wiederholen.

Anstatt zu erstellen:

class MyObjectList  {
   MyObject get(int index) {...}
}
class MyOtherObjectList  {
   MyOtherObject get(int index) {...}
}
class AnotherObjectList  {
   AnotherObject get(int index) {...}
}

Ich kann eine wiederverwendbare Klasse erstellen ... (für den Fall, dass Sie die rohe Sammlung aus irgendeinem Grund nicht verwenden möchten)

class MyList<T> {
   T get(int index) { ... }
}

Ich bin jetzt 3x effizienter und muss nur eine Kopie behalten. Warum möchten Sie nicht weniger Code pflegen?

Dies gilt auch für nicht erfassbare Klassen wie z Callable<T> oder ein Reference<T> das muss mit anderen Klassen interagieren. Willst du wirklich verlängern? Callable<T> und Future<T> und jede andere zugeordnete Klasse, um typsichere Versionen zu erstellen?

Ich nicht.


43
2018-06-04 23:56



Nicht zu typisieren ist einer der größten Vorteile von Java-Generika, da es während der Kompilierzeit eine Typprüfung durchführt. Dies wird die Möglichkeit reduzieren ClassCastExceptions, die zur Laufzeit ausgelöst werden können und zu robusterem Code führen können.

Aber ich vermute, dass du dir dessen voll bewusst bist.

Jedes Mal wenn ich auf Generics schaue, gibt es   mir Kopfschmerzen. Ich finde den besten Teil von   Java ist einfach und minimal   Syntax und Generika sind nicht einfach und   fügen Sie eine erhebliche Menge an neuen hinzu   Syntax.

Anfangs sah ich auch keinen Nutzen von Generika. Ich begann, Java aus der 1.4-Syntax zu lernen (obwohl Java 5 zu dieser Zeit nicht verfügbar war) und als ich auf Generika stieß, hatte ich das Gefühl, dass es mehr Code zum Schreiben gab und ich die Vorteile wirklich nicht verstand.

Moderne IDEs erleichtern das Schreiben von Code mit Generika.

Die meisten modernen, anständigen IDEs sind intelligent genug, um Code mit Generika zu schreiben, insbesondere mit Code-Vervollständigung.

Hier ist ein Beispiel für die Erstellung eines Map<String, Integer> mit einem HashMap. Der Code, den ich eingeben müsste, ist:

Map<String, Integer> m = new HashMap<String, Integer>();

Und tatsächlich, das ist eine Menge zu tippen, nur um ein neues zu machen HashMap. In Wirklichkeit musste ich jedoch nur so viel tippen, bevor Eclipse wusste, was ich brauchte:

Map<String, Integer> m = new Ha  Strg+Raum

Richtig, ich musste wählen HashMap aus einer Liste von Kandidaten, aber im Grunde wusste die IDE, was hinzuzufügen ist, einschließlich der generischen Typen. Mit den richtigen Werkzeugen ist die Verwendung von Generika nicht zu schlecht.

Da die Typen beim Abrufen von Elementen aus der generischen Auflistung bekannt sind, verhält sich die IDE außerdem so, als wäre dieses Objekt bereits ein Objekt des deklarierten Typs. Es ist nicht erforderlich, dass die IDE den Typ des Objekts erkennt ist.

Ein wichtiger Vorteil von Generika ist, dass es gut mit neuen Java 5-Funktionen zusammenspielt. Hier ist ein Beispiel für das Werfen von ganzen Zahlen in a Set und seine Summe berechnen:

Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);

int total = 0;
for (int i : set) {
  total += i;
}

In diesem Codeabschnitt sind drei neue Java 5-Features vorhanden:

Erstens erlauben Generika und Autoboxing von Primitiven folgende Zeilen:

set.add(10);
set.add(42);

Die Ganzzahl 10 ist in ein autoboxed Integer mit dem Wert von 10. (Und das gleiche für 42). Dann das Integer wird in die geworfen Set welches bekannt ist zu halten Integers. Versuche, einen zu werfen String würde einen Kompilierungsfehler verursachen.

Als Nächstes werden für for-each alle drei davon verwendet:

for (int i : set) {
  total += i;
}

Zuerst die Set enthalten Integers werden in einer for-each-Schleife verwendet. Jedes Element wird als ein deklariert int und das ist erlaubt als Integer wird auf das Primitiv zurückgesetzt int. Und die Tatsache, dass dieses Unboxing auftritt, ist bekannt, weil Generics verwendet wurden, um anzugeben, dass es solche gab Integers gehalten in der Set.

Generika können der Leim sein, der die neuen Funktionen von Java 5 zusammenbringt, und macht das Kodieren einfacher und sicherer. Und die meiste Zeit sind IDEs schlau genug, um Ihnen mit guten Vorschlägen zu helfen, also wird es nicht viel mehr tippen.

Und ehrlich gesagt, wie man aus der Set Zum Beispiel glaube ich, dass die Verwendung von Java 5-Funktionen den Code übersichtlicher und robuster machen kann.

Bearbeiten - Ein Beispiel ohne Generika

Das Folgende ist eine Illustration des Obigen Set Beispiel ohne den Einsatz von Generika. Es ist möglich, aber nicht gerade angenehm:

Set set = new HashSet();
set.add(10);
set.add(42);

int total = 0;
for (Object o : set) {
  total += (Integer)o;
}

(Hinweis: Der obige Code erzeugt eine unkontrollierte Konvertierungswarnung zur Kompilierungszeit.)

Wenn Sie Nicht-Generics-Sammlungen verwenden, sind die Typen, die in die Sammlung eingegeben werden, Objekte vom Typ Object. Daher wird in diesem Beispiel a Object ist was ist addEd in das Set.

set.add(10);
set.add(42);

In den obigen Zeilen ist Autoboxing im Spiel - das Primitiv int Wert 10 und 42 werden autoboxed in Integer Objekte, die dem hinzugefügt werden Set. Beachten Sie jedoch, die Integer Objekte werden wie behandelt Objects, da es keine Typinformationen gibt, die dem Compiler helfen, den Typ zu kennen Set sollte erwarten.

for (Object o : set) {

Dies ist der entscheidende Teil. Der Grund dafür, dass die for-each-Schleife funktioniert, ist, dass der Setimplementiert die Iterable Schnittstelle, die ein zurückgibt Iterator mit Typinformationen, falls vorhanden. (Iterator<T>, das ist.)

Da jedoch keine Typinformationen vorhanden sind, werden die Set wird zurückgeben Iterator die die Werte in der zurückgegeben werden Set wie Objects, und deshalb wird das Element in der for-each-Schleife abgerufen Muss von Typ sein Object.

Jetzt, dass die Object wird aus dem abgerufen Set, muss es zu einem umgewandelt werden Integer manuell, um den Zusatz durchzuführen:

  total += (Integer)o;

Hier erfolgt eine Typumwandlung von einem Object zu einem Integer. In diesem Fall wissen wir, dass dies immer funktioniert, aber die manuelle Typumwandlung macht immer das Gefühl, dass es fragiler Code ist, der beschädigt werden könnte, wenn eine kleine Änderung an anderer Stelle vorgenommen wird. (Ich fühle, dass jede Typumwandlung eine ist ClassCastException warten, um zu passieren, aber ich schweife ab ...)

Das Integer ist jetzt in ein unboxed int und erlaubt, die Zugabe in die int Variable total.

Ich hoffe, ich konnte veranschaulichen, dass die neuen Funktionen von Java 5 mit nicht-generischem Code verwendet werden können, aber es ist einfach nicht so sauber und unkompliziert wie das Schreiben von Code mit Generika. Und um die neuen Features von Java 5 voll ausschöpfen zu können, sollte man sich zumindest mit Generika beschäftigen, die zumindest Kompilierzeitprüfungen erlauben, um zu verhindern, dass ungültige Typumwandlungen zur Laufzeit Ausnahmen auslösen.


18
2018-06-05 00:24



Wenn Sie die Java-Bug-Datenbank kurz vor der Veröffentlichung von 1.5 durchsuchen würden, würden Sie sieben Mal mehr Fehler finden NullPointerException als ClassCastException. Es scheint also nicht, dass es ein großartiges Feature ist, um Bugs zu finden, oder zumindest Bugs, die nach einem kleinen Rauchtest bestehen bleiben.

Für mich ist der große Vorteil von Generika, dass sie dokumentieren in Code wichtige Typinformationen Wenn ich nicht wollte, dass diese Art von Informationen im Code dokumentiert wird, dann würde ich eine dynamisch typisierte Sprache oder zumindest eine Sprache mit impliziterer Art der Inferenz verwenden.

Die Sammlung eines Objekts zu behalten ist kein schlechter Stil (aber der übliche Stil besteht darin, die Kapselung effektiv zu ignorieren). Es hängt eher davon ab, was Sie tun. Das Übergeben von Sammlungen an "Algorithmen" ist etwas einfacher (zu oder vor der Kompilierzeit) mit Generika zu überprüfen.


15
2018-06-04 23:43



Generics in Java erleichtern parametrischer Polymorphismus. Über Typparameter können Sie Argumente an Typen übergeben. Genauso wie eine Methode String foo(String s) modelliert ein Verhalten, nicht nur für eine bestimmte Zeichenfolge, sondern für eine beliebige Zeichenfolge s, so ein Typ wie List<T> modelliert etwas Verhalten, nicht nur für einen bestimmten Typ, sondern für jeden Typ. List<T> sagt, dass für jeden Typ TEs gibt eine Art von List wessen Elemente sind Ts. Damit List ist eigentlich ein a Typkonstruktor. Es nimmt einen Typ als Argument und konstruiert als Ergebnis einen anderen Typ.

Hier sind ein paar Beispiele für generische Typen, die ich jeden Tag verwende. Erstens, eine sehr nützliche generische Schnittstelle:

public interface F<A, B> {
  public B f(A a);
}

Diese Schnittstelle sagt das für einige zwei Arten, A und B, es gibt eine Funktion (genannt f) Das dauert ein A und gibt a zurück B. Wenn Sie diese Schnittstelle implementieren, A und B können alle gewünschten Typen sein, solange Sie eine Funktion bereitstellen f das nimmt das erstere und gibt das letztere zurück. Hier ist eine Beispielimplementierung der Schnittstelle:

F<Integer, String> intToString = new F<Integer, String>() {
  public String f(int i) {
    return String.valueOf(i);
  }
}

Vor Generika wurde Polymorphismus erreicht durch Unterklasse Verwendung der extends Stichwort. Mit Generika können wir tatsächlich auf Unterklassen verzichten und stattdessen parametrischen Polymorphismus verwenden. Betrachten Sie zum Beispiel eine parametrisierte (generische) Klasse, die zum Berechnen von Hash-Codes für jeden Typ verwendet wird. Anstatt Object.hashCode () zu überschreiben, würden wir eine generische Klasse wie diese verwenden:

public final class Hash<A> {
  private final F<A, Integer> hashFunction;

  public Hash(final F<A, Integer> f) {
    this.hashFunction = f;
  }

  public int hash(A a) {
    return hashFunction.f(a);
  }
}

Dies ist viel flexibler als die Verwendung der Vererbung, da wir bei der Verwendung von Komposition und parametrischem Polymorphismus bleiben können, ohne spröde Hierarchien zu blockieren.

Javas Generika sind jedoch nicht perfekt. Sie können über Typen abstrahieren, aber Sie können nicht zum Beispiel über Typkonstruktoren abstrahieren. Das heißt, Sie können "für jeden Typ T" sagen, aber Sie können nicht sagen "für irgendeinen Typ T, der einen Typparameter A nimmt".

Ich habe hier einen Artikel über diese Grenzen von Java-Generika geschrieben.

Ein großer Gewinn bei Generika ist, dass sie das Unterklassifizieren vermeiden. Subclassing führt tendenziell zu spröden Klassenhierarchien, die schwer zu erweitern sind, und zu Klassen, die einzeln schwer zu verstehen sind, ohne die gesamte Hierarchie zu betrachten.

Vor Generika könnten Sie Klassen wie Widget erweitert um FooWidget, BarWidget, und BazWidgetMit Generika können Sie eine einzige generische Klasse haben Widget<A> das dauert a Foo, Bar oder Baz in seinem Konstruktor, um Ihnen zu geben Widget<Foo>, Widget<Bar>, und Widget<Baz>.


10
2018-06-05 00:39



Generika vermeiden den Leistungseinbruch beim Boxen und Unboxing. Sehen Sie sich ArrayList vs List <T> an. Beide machen die gleichen Kernpunkte, aber List <T> wird viel schneller sein, weil Sie nicht zu / von einem Objekt boxen müssen.


7
2017-09-16 22:00



Ich mag sie einfach, weil sie Ihnen einen schnellen Weg geben, einen benutzerdefinierten Typ zu definieren (wie ich sie sowieso benutze).

Anstatt beispielsweise eine Struktur zu definieren, die aus einer Zeichenfolge und einer Ganzzahl besteht, und dann eine ganze Reihe von Objekten und Methoden implementieren muss, um auf ein Array dieser Strukturen usw. zuzugreifen, können Sie einfach ein Dictionary erstellen

Dictionary<int, string> dictionary = new Dictionary<int, string>();

Und der Compiler / IDE erledigt den Rest des schweren Hebens. In einem Wörterbuch können Sie den ersten Typ als Schlüssel verwenden (keine wiederholten Werte).


5
2017-09-16 22:02



Der größte Vorteil für Generics ist die Wiederverwendung von Code. Nehmen wir an, Sie haben viele Geschäftsobjekte, und Sie werden SEHR ähnlichen Code für jede Entität schreiben, um dieselben Aktionen auszuführen. (I.E Linq zu SQL-Operationen).

Mit Generics können Sie eine Klasse erstellen, die in der Lage ist, einen der Typen zu verwenden, die von einer bestimmten Basisklasse erben, oder eine bestimmte Schnittstelle wie folgt zu implementieren:

public interface IEntity
{

}

public class Employee : IEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int EmployeeID { get; set; }
}

public class Company : IEntity
{
    public string Name { get; set; }
    public string TaxID { get; set }
}

public class DataService<ENTITY, DATACONTEXT>
    where ENTITY : class, IEntity, new()
    where DATACONTEXT : DataContext, new()
{

    public void Create(List<ENTITY> entities)
    {
        using (DATACONTEXT db = new DATACONTEXT())
        {
            Table<ENTITY> table = db.GetTable<ENTITY>();

            foreach (ENTITY entity in entities)
                table.InsertOnSubmit (entity);

            db.SubmitChanges();
        }
    }
}

public class MyTest
{
    public void DoSomething()
    {
        var dataService = new DataService<Employee, MyDataContext>();
        dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
        var otherDataService = new DataService<Company, MyDataContext>();
            otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });

    }
}

Beachten Sie die Wiederverwendung des gleichen Dienstes bei den verschiedenen Typen in der obigen DoSomething-Methode. Wirklich elegant!

Es gibt viele andere gute Gründe, Generika für Ihre Arbeit zu verwenden, das ist mein Favorit.


5
2017-09-17 04:03