Was sind die Unterschiede zwischen Generika in C # und Java… und Vorlagen in C ++? [geschlossen]


203

Ich benutze meistens Java und Generika sind relativ neu. Ich lese immer wieder, dass Java die falsche Entscheidung getroffen hat oder dass .NET bessere Implementierungen usw. usw. hat.

Was sind die Hauptunterschiede zwischen C ++, C # und Java bei Generika? Vor- / Nachteile von jedem?

Antworten:


364

Ich werde dem Geräusch meine Stimme hinzufügen und versuchen, die Dinge klar zu machen:

Mit C # Generics können Sie so etwas deklarieren.

List<Person> foo = new List<Person>();

und dann verhindert der Compiler, dass Sie Dinge einfügen, die nicht Personin der Liste enthalten sind.
Hinter den Kulissen List<Person>fügt der C # -Compiler nur die .NET-DLL-Datei ein, aber zur Laufzeit erstellt der JIT-Compiler einen neuen Code, als hätten Sie eine spezielle Listenklasse geschrieben, die nur Personen enthält - so etwas wie ListOfPerson.

Dies hat den Vorteil, dass es sehr schnell geht. Es gibt kein Casting oder andere Dinge, und da die DLL die Informationen enthält, von denen dies eine Liste ist Person, kann anderer Code, der sie später mithilfe von Reflection betrachtet, erkennen, dass sie PersonObjekte enthält (so dass Sie Intellisense erhalten und so weiter).

Der Nachteil dabei ist, dass alter C # 1.0- und 1.1-Code (bevor Generika hinzugefügt wurden) diese neuen nicht versteht. List<something>Sie müssen die Dinge also manuell wieder in einfache alte konvertieren List, um mit ihnen zusammenarbeiten zu können. Dies ist kein so großes Problem, da der C # 2.0-Binärcode nicht abwärtskompatibel ist. Dies kann nur passieren, wenn Sie einen alten C # 1.0 / 1.1-Code auf C # 2.0 aktualisieren

Mit Java Generics können Sie so etwas deklarieren.

ArrayList<Person> foo = new ArrayList<Person>();

An der Oberfläche sieht es genauso aus und ist es auch. Der Compiler verhindert auch, dass Sie Dinge einfügen, die nicht Personin der Liste enthalten sind.

Der Unterschied ist, was hinter den Kulissen passiert. Im Gegensatz zu C # erstellt Java kein spezielles Programm ListOfPerson- es verwendet nur das einfache alte, ArrayListdas immer in Java verwendet wurde. Wenn Sie Dinge aus dem Array herausholen, muss der übliche Person p = (Person)foo.get(1);Casting-Tanz noch durchgeführt werden. Der Compiler speichert Ihnen die Tastendrücke, aber der Geschwindigkeitstreffer / das Casting erfolgt immer noch so, wie es immer war.
Wenn Leute "Type Erasure" erwähnen, ist dies das, worüber sie sprechen. Der Compiler fügt die Casts für Sie ein und "löscht" dann die Tatsache, dass es sich Personnicht nur um eine Liste handeltObject

Der Vorteil dieses Ansatzes ist, dass alter Code, der Generika nicht versteht, sich nicht darum kümmern muss. Es geht immer noch um das gleiche alte ArrayListwie immer. Dies ist in der Java-Welt wichtiger, da sie das Kompilieren von Code mit Java 5 mit Generika unterstützen und auf alten 1.4- oder früheren JVMs ausführen wollten, mit denen sich Microsoft bewusst nicht befasst hat.

Der Nachteil ist der Geschwindigkeitstreffer, den ich zuvor erwähnt habe, und auch, weil ListOfPersonin den .class-Dateien keine Pseudoklasse oder ähnliches enthalten ist, Code, der sie später betrachtet (mit Reflexion oder wenn Sie sie aus einer anderen Sammlung ziehen) wo es konvertiert wurde Objectoder so weiter) kann in keiner Weise sagen, dass es eine Liste sein soll, die nur Personund nicht irgendeine andere Array-Liste enthält.

Mit C ++ - Vorlagen können Sie so etwas deklarieren

std::list<Person>* foo = new std::list<Person>();

Es sieht aus wie C # - und Java-Generika und es wird das tun, was Sie denken, aber hinter den Kulissen passieren verschiedene Dinge.

Es hat das meiste mit C # -Generika gemeinsam, da es etwas Besonderes erstellt, pseudo-classesanstatt nur die Typinformationen wegzuwerfen, wie es Java tut, aber es ist ein ganz anderer Fischkessel.

Sowohl C # als auch Java erzeugen eine Ausgabe, die für virtuelle Maschinen entwickelt wurde. Wenn Sie einen Code schreiben, der eine PersonKlasse enthält, werden in beiden Fällen einige Informationen zu einer PersonKlasse in die DLL- oder .class-Datei aufgenommen, und die JVM / CLR erledigt dies.

C ++ erzeugt rohen x86-Binärcode. Alles ist kein Objekt und es gibt keine zugrunde liegende virtuelle Maschine, die etwas über eine PersonKlasse wissen muss . Es gibt kein Boxen oder Unboxing und Funktionen müssen nicht zu Klassen gehören oder so.

Aus diesem Grund schränkt der C ++ - Compiler die Möglichkeiten von Vorlagen nicht ein. Grundsätzlich können Sie bei jedem Code, den Sie manuell schreiben können, Vorlagen für Sie schreiben lassen.
Das offensichtlichste Beispiel ist das Hinzufügen von Dingen:

In C # und Java muss das generische System wissen, welche Methoden für eine Klasse verfügbar sind, und dies muss an die virtuelle Maschine weitergegeben werden. Die einzige Möglichkeit, dies festzustellen, besteht darin, entweder die tatsächliche Klasse fest zu codieren oder Schnittstellen zu verwenden. Beispielsweise:

string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }

Dieser Code wird nicht in C # oder Java kompiliert, da er nicht weiß, dass der Typ Ttatsächlich eine Methode namens Name () bereitstellt. Sie müssen es sagen - in C # wie folgt:

interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }

Und dann müssen Sie sicherstellen, dass die Dinge, die Sie an addNames übergeben, die IHasName-Schnittstelle implementieren und so weiter. Die Java-Syntax ist unterschiedlich ( <T extends IHasName>), weist jedoch dieselben Probleme auf.

Der "klassische" Fall für dieses Problem ist der Versuch, eine Funktion zu schreiben, die dies tut

string addNames<T>( T first, T second ) { return first + second; }

Sie können diesen Code nicht schreiben, da es keine Möglichkeit gibt, eine Schnittstelle mit der darin enthaltenen +Methode zu deklarieren . Du scheiterst.

C ++ leidet unter keinem dieser Probleme. Der Compiler kümmert sich nicht darum, Typen an VMs weiterzugeben. Wenn beide Objekte über die Funktion .Name () verfügen, wird er kompiliert. Wenn nicht, wird es nicht. Einfach.

Also, da hast du es :-)


8
Die generierte Pseudoklasie für Referenztypen in C # hat dieselbe Implementierung, sodass Sie nicht genau ListOfPeople erhalten. Schauen Sie sich blogs.msdn.com/ericlippert/archive/2009/07/30/…
Piotr Czapla

4
Nein, Sie können nicht kompilieren Java 5 - Code die Verwendung von Generika, und haben es auf alten 1.4 VMs laufen (zumindest die Sun JDK nicht diese implementieren. Einige 3rd - Party - Tools zu tun.) Was können Sie tun Verwendung zuvor kompiliert 1,4 JAR - Dateien aus 1,5 / 1,6 Code.
Finnw

4
Ich lehne die Aussage ab, dass Sie nicht int addNames<T>( T first, T second ) { return first + second; }in C # schreiben können . Der generische Typ kann auf eine Klasse anstelle einer Schnittstelle beschränkt werden, und es gibt eine Möglichkeit, eine Klasse mit dem +Operator darin zu deklarieren .
Mashmagar

4
@ AlexanderMalakhov es ist nicht idiomatisch mit Absicht. Es ging nicht darum, sich über die Redewendungen von C ++ zu informieren, sondern zu veranschaulichen, wie der gleiche Code von jeder Sprache unterschiedlich behandelt wird. Dieses Ziel wäre schwieriger zu erreichen gewesen, je unterschiedlicher der Code aussieht
Orion Edwards

3
@phresnel Ich stimme im Prinzip zu, aber wenn ich dieses Snippet in idiomatischem C ++ geschrieben hätte, wäre es für C # / Java-Entwickler weitaus weniger verständlich und hätte daher (glaube ich) schlechtere Arbeit geleistet, um den Unterschied zu erklären. Lassen Sie uns zustimmen, in diesem Punkt nicht einverstanden zu sein :-)
Orion Edwards

61

C ++ verwendet selten die Terminologie "Generika". Stattdessen wird das Wort "Vorlagen" verwendet und ist genauer. Vorlagen beschreiben eine Technik , um ein generisches Design zu erzielen.

C ++ - Vorlagen unterscheiden sich aus zwei Hauptgründen stark von der Implementierung von C # und Java. Der erste Grund ist, dass C ++ - Vorlagen nicht nur Argumente vom Typ zur Kompilierungszeit zulassen, sondern auch Argumente zum Konstantenwert zur Kompilierungszeit: Vorlagen können als Ganzzahlen oder sogar als Funktionssignaturen angegeben werden. Dies bedeutet, dass Sie zur Kompilierungszeit einige ziemlich funky Dinge tun können, z. B. Berechnungen:

template <unsigned int N>
struct product {
    static unsigned int const VALUE = N * product<N - 1>::VALUE;
};

template <>
struct product<1> {
    static unsigned int const VALUE = 1;
};

// Usage:
unsigned int const p5 = product<5>::VALUE;

Dieser Code verwendet auch die andere herausragende Funktion von C ++ - Vorlagen, nämlich die Vorlagenspezialisierung. Der Code definiert eine Klassenvorlage productmit einem Wertargument. Außerdem wird eine Spezialisierung für diese Vorlage definiert, die immer dann verwendet wird, wenn das Argument 1 ergibt. Auf diese Weise kann ich eine Rekursion über Vorlagendefinitionen definieren. Ich glaube, dass dies zuerst von Andrei Alexandrescu entdeckt wurde .

Die Vorlagenspezialisierung ist für C ++ wichtig, da sie strukturelle Unterschiede in den Datenstrukturen berücksichtigt. Vorlagen als Ganzes sind ein Mittel zur Vereinheitlichung einer Schnittstelle zwischen verschiedenen Typen. Obwohl dies wünschenswert ist, können nicht alle Typen innerhalb der Implementierung gleich behandelt werden. C ++ - Vorlagen berücksichtigen dies. Dies ist fast der gleiche Unterschied, den OOP zwischen Schnittstelle und Implementierung beim Überschreiben virtueller Methoden macht.

C ++ - Vorlagen sind für das algorithmische Programmierparadigma von wesentlicher Bedeutung. Beispielsweise sind fast alle Algorithmen für Container als Funktionen definiert, die den Containertyp als Vorlagentyp akzeptieren und einheitlich behandeln. Eigentlich ist das nicht ganz richtig: C ++ funktioniert nicht für Container, sondern für Bereiche , die von zwei Iteratoren definiert werden und auf den Anfang und hinter das Ende des Containers zeigen. Somit wird der gesamte Inhalt von den Iteratoren umschrieben: begin <= elements <end.

Die Verwendung von Iteratoren anstelle von Containern ist nützlich, da damit Teile eines Containers anstatt insgesamt bearbeitet werden können.

Ein weiteres Unterscheidungsmerkmal von C ++ ist die Möglichkeit einer teilweisen Spezialisierung für Klassenvorlagen. Dies hängt etwas mit dem Mustervergleich für Argumente in Haskell und anderen funktionalen Sprachen zusammen. Betrachten wir zum Beispiel eine Klasse, die Elemente speichert:

template <typename T>
class Store {  }; // (1)

Dies funktioniert für jeden Elementtyp. Angenommen, wir können Zeiger effizienter als andere Typen speichern, indem wir einen speziellen Trick anwenden. Wir können dies tun, indem wir uns teilweise auf alle Zeigertypen spezialisieren:

template <typename T>
class Store<T*> {  }; // (2)

Wenn wir jetzt eine Containervorlage für einen Typ instanziieren, wird die entsprechende Definition verwendet:

Store<int> x; // Uses (1)
Store<int*> y; // Uses (2)
Store<string**> z; // Uses (2), with T = string*.

Ich habe mir manchmal gewünscht, dass die generische Funktion in .net die Verwendung von Dingen außer Typen als Schlüssel ermöglichen könnte. Wenn Arrays vom Werttyp Teil des Frameworks wären (ich bin überrascht, dass sie in gewisser Weise nicht mit älteren APIs interagieren müssen, die Arrays mit fester Größe in Strukturen einbetten), wäre es nützlich, a zu deklarieren Klasse, die einige einzelne Elemente und dann ein Array vom Werttyp enthielt, dessen Größe ein generischer Parameter war. So wie es ist, kann es am nächsten kommen, ein Klassenobjekt zu haben, das die einzelnen Elemente enthält und dann auch einen Verweis auf ein separates Objekt enthält, das das Array enthält.
Supercat

@supercat Wenn Sie mit der Legacy-API interagieren, sollten Sie Marshalling verwenden (das über Attribute mit Anmerkungen versehen werden kann). Die CLR hat ohnehin keine Arrays mit fester Größe, daher wäre es hier nicht hilfreich, Vorlagenargumente ohne Typ zu haben.
Konrad Rudolph

Ich denke, was ich rätselhaft finde, ist, dass es so scheint, als ob Arrays mit festem Werttyp nicht schwierig gewesen wären, und dass viele Datentypen eher nach Referenz als nach Wert sortiert werden könnten. Während Marshall-by-Value in Fällen nützlich sein kann, die wirklich nicht anders gehandhabt werden können, würde ich Marshal-by-Ref in fast allen Fällen, in denen es verwendbar ist, als überlegen betrachten, sodass solche Fälle Strukturen mit festen Werten enthalten können Arrays in der Größe wären eine nützliche Funktion gewesen.
Supercat

Übrigens wäre eine andere Situation, in der nicht typische generische Parameter nützlich wären, Datentypen, die dimensionierte Größen darstellen. Man könnte Dimensionsinformationen in Instanzen einschließen, die Mengen darstellen, aber wenn solche Informationen in einem Typ vorhanden sind, kann man angeben, dass eine Sammlung Objekte enthalten soll, die eine bestimmte dimensionierte Einheit darstellen.
Supercat


18

Es gibt bereits viele gute Antworten auf , was die Unterschiede sind, so lassen Sie mich eine etwas andere Perspektive geben und fügen Sie das warum .

Wie bereits erläutert, besteht der Hauptunterschied in der Typlöschung , dh der Tatsache, dass der Java-Compiler die generischen Typen löscht und sie nicht im generierten Bytecode landen. Die Frage ist jedoch: Warum sollte jemand das tun? Es macht keinen Sinn! Oder doch?

Was ist die Alternative? Wenn Sie keine Generika in der Sprache implementieren, wo sie implementieren Sie sie? Und die Antwort lautet: in der virtuellen Maschine. Was die Abwärtskompatibilität bricht.

Mit Type Erasure hingegen können Sie generische Clients mit nicht generischen Bibliotheken mischen. Mit anderen Worten: Code, der auf Java 5 kompiliert wurde, kann weiterhin auf Java 1.4 bereitgestellt werden.

Microsoft hat jedoch beschlossen, die Abwärtskompatibilität für Generika zu unterbrechen. Deshalb sind .NET-Generika "besser" als Java-Generika.

Natürlich sind Sun keine Idioten oder Feiglinge. Der Grund, warum sie "durchgeknallt" waren, war, dass Java bei der Einführung von Generika deutlich älter und weiter verbreitet war als .NET. (Sie wurden in beiden Welten ungefähr zur gleichen Zeit eingeführt.) Die Abwärtskompatibilität zu brechen, wäre ein großer Schmerz gewesen.

Anders ausgedrückt: In Java sind Generika Teil der Sprache (dh sie gelten nur für Java, nicht für andere Sprachen), in .NET sind sie Teil der virtuellen Maschine (dh sie gelten nicht für alle Sprachen) nur C # und Visual Basic.NET).

Vergleichen Sie dies mit .NET wie LINQ verfügt, Lambda - Ausdrücke, lokale Variable Typinferenz, anonyme Typen und Ausdrucksbäume: Das sind alle Sprachfunktionen. Aus diesem Grund gibt es subtile Unterschiede zwischen VB.NET und C #: Wenn diese Funktionen Teil der VM wären, wären sie in allen Sprachen gleich. Die CLR hat sich jedoch nicht geändert: In .NET 3.5 SP1 ist sie immer noch dieselbe wie in .NET 2.0. Sie können ein C # -Programm, das LINQ verwendet, mit dem .NET 3.5-Compiler kompilieren und es dennoch unter .NET 2.0 ausführen, sofern Sie keine .NET 3.5-Bibliotheken verwenden. Das würde nicht mit Generika und .NET 1.1 funktionieren , aber es würde mit Java und Java 1.4 funktionieren.


3
LINQ ist in erster Linie eine Bibliotheksfunktion (obwohl C # und VB nebenher auch Syntaxzucker hinzugefügt haben). Jede Sprache, die auf die 2.0 CLR abzielt, kann LINQ vollständig nutzen, indem Sie einfach die System.Core-Assembly laden.
Richard Berg

Ja, tut mir leid, ich hätte klarer sein sollen. LINQ. Ich bezog mich auf die Abfragesyntax, nicht auf die monadischen Standardabfrageoperatoren, die LINQ-Erweiterungsmethoden oder die IQueryable-Schnittstelle. Natürlich können Sie diese aus jeder .NET-Sprache verwenden.
Jörg W Mittag

1
Ich denke an eine andere Option für Java. Selbst wenn Oracle die Abwärtskompatibilität nicht beeinträchtigen möchte, können sie dennoch einen Compilertrick ausführen, um zu verhindern, dass Typinformationen gelöscht werden. Beispielsweise,ArrayList<T> kann mit einem (versteckten) statisch als neuer intern genannten Art emittiert wird Class<T>Feld. Solange die neue Version von generic lib mit mehr als 1,5 Byte Code bereitgestellt wurde, kann sie auf 1.4-JVMs ausgeführt werden.
Earth Engine

14

Follow-up zu meinem vorherigen Beitrag.

Vorlagen sind einer der Hauptgründe, warum C ++ bei Intellisense unabhängig von der verwendeten IDE so miserabel ausfällt. Aufgrund der Vorlagenspezialisierung kann die IDE nie wirklich sicher sein, ob ein bestimmtes Mitglied existiert oder nicht. Erwägen:

template <typename T>
struct X {
    void foo() { }
};

template <>
struct X<int> { };

typedef int my_int_type;

X<my_int_type> a;
a.|

Jetzt befindet sich der Cursor an der angegebenen Position und es ist verdammt schwer für die IDE zu diesem Zeitpunkt zu sagen, ob und was Mitglieder ahaben. Für andere Sprachen wäre das Parsen unkompliziert, aber für C ++ ist vorher einiges an Evaluierung erforderlich.

Es wird schlimmer. Was wäre, wenn my_int_typesie auch in einer Klassenvorlage definiert wären ? Jetzt würde sein Typ von einem anderen Typargument abhängen. Und hier scheitern sogar Compiler.

template <typename T>
struct Y {
    typedef T my_type;
};

X<Y<int>::my_type> b;

Nach einigem Nachdenken würde ein Programmierer zu dem Schluss kommen, dass dieser Code derselbe wie der oben genannte ist: Er Y<int>::my_typelöst sich auf intund bsollte daher vom selben Typ sein wie a, richtig?

Falsch. An dem Punkt, an dem der Compiler versucht, diese Anweisung aufzulösen, weiß er es noch Y<int>::my_typenicht! Daher weiß es nicht, dass dies ein Typ ist. Es könnte etwas anderes sein, z. B. eine Mitgliedsfunktion oder ein Feld. Dies kann zu Unklarheiten führen (im vorliegenden Fall jedoch nicht), weshalb der Compiler fehlschlägt. Wir müssen ausdrücklich darauf hinweisen, dass wir uns auf einen Typnamen beziehen:

X<typename Y<int>::my_type> b;

Jetzt wird der Code kompiliert. Beachten Sie den folgenden Code, um zu sehen, wie sich aus dieser Situation Mehrdeutigkeiten ergeben:

Y<int>::my_type(123);

Diese Code-Anweisung ist vollkommen gültig und weist C ++ an, den Funktionsaufruf an auszuführen Y<int>::my_type. Wenn my_typees sich jedoch nicht um eine Funktion, sondern um einen Typ handelt, ist diese Anweisung weiterhin gültig und führt eine spezielle Umwandlung (die Umwandlung im Funktionsstil) durch, bei der es sich häufig um einen Konstruktoraufruf handelt. Der Compiler kann nicht sagen, was wir meinen, also müssen wir hier klarstellen.


2
Ich stimme zu. Es gibt jedoch einige Hoffnung. Das Autocompletion-System und der C ++ - Compiler müssen sehr eng zusammenarbeiten. Ich bin mir ziemlich sicher, dass Visual Studio niemals über eine solche Funktion verfügen wird, aber in Eclipse / CDT oder einer anderen auf GCC basierenden IDE können Dinge passieren. HOFFNUNG ! :)
Benoît

6

Sowohl Java als auch C # führten Generika nach ihrer ersten Sprachveröffentlichung ein. Es gibt jedoch Unterschiede darin, wie sich die Kernbibliotheken bei der Einführung von Generika geändert haben. Die Generika von C # sind nicht nur Compilermagie und konnten daher nicht generiert werden vorhandene Bibliotheksklassen , ohne die Abwärtskompatibilität .

Zum Beispiel in Java die bestehenden Collections Framework wurde vollständig genericised . Java verfügt nicht über eine generische und eine nicht generische Legacy-Version der Sammlungsklassen. In mancher Hinsicht ist dies viel sauberer - wenn Sie eine Sammlung in C # verwenden müssen, gibt es kaum einen Grund, sich für die nicht generische Version zu entscheiden, aber diese Legacy-Klassen bleiben bestehen und überladen die Landschaft.

Ein weiterer bemerkenswerter Unterschied sind die Enum-Klassen in Java und C #. Javas Enum hat diese etwas gewundene Definition:

//  java.lang.Enum Definition in Java
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {

(Siehe Angelika Langers sehr klare Erklärung, warum dies so ist. Im Wesentlichen bedeutet dies, dass Java typsicheren Zugriff von einer Zeichenfolge auf seinen Enum-Wert gewähren kann:

//  Parsing String to Enum in Java
Colour colour = Colour.valueOf("RED");

Vergleichen Sie dies mit der Version von C #:

//  Parsing String to Enum in C#
Colour colour = (Colour)Enum.Parse(typeof(Colour), "RED");

Da Enum bereits in C # vorhanden war, bevor Generika in die Sprache eingeführt wurden, konnte die Definition nicht geändert werden, ohne den vorhandenen Code zu beschädigen. Wie Sammlungen verbleibt es in diesem Kernzustand in den Kernbibliotheken.


Selbst die Generika von C # sind nicht nur Compiler-Magie, der Compiler kann weitere Magie ausführen, um vorhandene Bibliotheken zu generieren. Es gibt keinen Grund , warum sie umbenennen müssen , ArrayListum List<T>es in einen neuen Namensraum zu setzen. Tatsache ist, dass im Quellcode eine Klasse angezeigt wird, da ArrayList<T>diese im IL-Code zu einem anderen vom Compiler generierten Klassennamen wird, sodass keine Namenskonflikte auftreten können.
Earth Engine

4

11 Monate zu spät, aber ich denke, diese Frage ist bereit für einige Java Wildcard-Sachen.

Dies ist eine syntaktische Funktion von Java. Angenommen, Sie haben eine Methode:

public <T> void Foo(Collection<T> thing)

Angenommen, Sie müssen sich nicht auf den Typ T im Methodenkörper beziehen. Sie deklarieren einen Namen T und verwenden ihn dann nur einmal. Warum sollten Sie sich also einen Namen dafür überlegen müssen? Stattdessen können Sie schreiben:

public void Foo(Collection<?> thing)

Das Fragezeichen fordert den Compiler auf, so zu tun, als hätten Sie einen normalen benannten Typparameter deklariert, der nur einmal an dieser Stelle erscheinen muss.

Mit Platzhaltern können Sie nichts tun, was Sie auch nicht mit einem benannten Typparameter tun können (so werden diese Dinge immer in C ++ und C # ausgeführt).


2
Weitere 11 Monate zu spät ... Es gibt Dinge, die Sie mit Java-Platzhaltern tun können, die Sie mit benannten Typparametern nicht können. Sie können dies in Java tun: class Foo<T extends List<?>>und verwenden, Foo<StringList>aber in C # müssen Sie diesen zusätzlichen Typparameter hinzufügen: class Foo<T, T2> where T : IList<T2>und den klobigen verwenden Foo<StringList, String>.
R. Martinho Fernandes



1

C ++ - Vorlagen sind tatsächlich viel leistungsfähiger als ihre C # - und Java-Gegenstücke, da sie zur Kompilierungszeit ausgewertet werden und die Spezialisierung unterstützen. Dies ermöglicht die Template-Meta-Programmierung und macht den C ++ - Compiler gleichbedeutend mit einer Turing-Maschine (dh während des Kompilierungsprozesses können Sie alles berechnen, was mit einer Turing-Maschine berechenbar ist).


1

In Java sind Generika nur auf Compilerebene verfügbar, sodass Sie Folgendes erhalten:

a = new ArrayList<String>()
a.getClass() => ArrayList

Beachten Sie, dass der Typ von 'a' eine Array-Liste ist, keine Liste von Zeichenfolgen. Die Art einer Liste von Bananen würde also einer Liste von Affen entsprechen.

Sozusagen.


1

Es sieht so aus, als ob es neben anderen sehr interessanten Vorschlägen einen gibt, bei dem es darum geht, Generika zu verfeinern und die Abwärtskompatibilität zu brechen:

Derzeit werden Generika mithilfe des Löschvorgangs implementiert. Dies bedeutet, dass die generischen Typinformationen zur Laufzeit nicht verfügbar sind, was das Schreiben von Code erschwert. Auf diese Weise wurden Generika implementiert, um die Abwärtskompatibilität mit älterem nicht generischem Code zu unterstützen. Reified Generics würden die generischen Typinformationen zur Laufzeit verfügbar machen, wodurch nicht generischer Legacy-Code beschädigt würde. Neal Gafter hat jedoch vorgeschlagen, Typen nur dann reifizierbar zu machen, wenn dies angegeben ist, um die Abwärtskompatibilität nicht zu beeinträchtigen.

bei Alex Millers Artikel über Java 7-Vorschläge


0

NB: Ich habe nicht genug Punkte, um Kommentare abzugeben. Sie können diese also als Kommentar zu einer geeigneten Antwort verschieben.

Entgegen der landläufigen Meinung, von der ich nie verstehe, woher sie stammt, hat .net echte Generika implementiert, ohne die Abwärtskompatibilität zu beeinträchtigen, und sie haben sich explizit darum bemüht. Sie müssen Ihren nicht generischen .net 1.0-Code nicht in generische Codes ändern, um in .net 2.0 verwendet zu werden. Sowohl die generischen als auch die nicht generischen Listen sind in .Net Framework 2.0 auch bis 4.0 noch verfügbar, genau aus Gründen der Abwärtskompatibilität. Daher funktionieren alte Codes, die noch nicht generische ArrayList verwendeten, weiterhin und verwenden dieselbe ArrayList-Klasse wie zuvor. Die Abwärtscode-Kompatibilität wird seit 1.0 bis jetzt immer beibehalten ... Selbst in .net 4.0 müssen Sie die Option verwenden, eine nicht generische Klasse ab 1.0 BCL zu verwenden, wenn Sie dies möchten.

Ich denke also nicht, dass Java die Abwärtskompatibilität brechen muss, um echte Generika zu unterstützen.


Das ist nicht die Art von Abwärtskompatibilität, von der die Leute sprechen. Die Idee ist die Abwärtskompatibilität für die Laufzeit : Mit Generics in .NET 2.0 geschriebener Code kann nicht auf älteren Versionen von .NET Framework / CLR ausgeführt werden. Wenn Java "echte" Generika einführen würde, könnte neuerer Java-Code nicht auf älteren JVMs ausgeführt werden (da Änderungen am Bytecode erforderlich sind).
Zaman

Das ist .net, keine Generika. Erfordert immer eine Neukompilierung, um auf eine bestimmte CLR-Version abzuzielen. Es gibt Bytecode-Kompatibilität, es gibt Code-Kompatibilität. Außerdem antwortete ich speziell auf die Notwendigkeit, alten Code, der alte Liste verwendete, zu konvertieren, um die neue generische Liste zu verwenden, was überhaupt nicht wahr ist.
Sheepy

1
Ich denke, die Leute reden über Vorwärtskompatibilität . Dh .net 2.0-Code, der auf .net 1.1 ausgeführt werden soll, der unterbrochen wird, weil die 1.1-Laufzeit nichts über 2.0 "Pseudoklasse" weiß. Sollte es dann nicht sein, dass "Java keine echten Generika implementiert, weil sie die Vorwärtskompatibilität aufrechterhalten wollen"? (anstatt rückwärts)
Sheepy

Kompatibilitätsprobleme sind subtil. Ich glaube nicht, dass das Problem darin bestand, dass das Hinzufügen von "echten" Generika zu Java alle Programme betreffen würde, die ältere Versionen von Java verwenden, sondern dass Code, der "neue, verbesserte" Generika verwendet, Schwierigkeiten haben würde, solche Objekte mit älterem Code auszutauschen wusste nichts über die neuen Typen. Angenommen, ein Programm hat eine ArrayList<Foo>, die es an eine ältere Methode übergeben möchte, die eine ArrayListmit Instanzen von füllen soll Foo. Wenn ein ArrayList<foo>nicht ist ArrayList, wie bringt man das zum Laufen ?
Supercat
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.