Der Zwiespalt: low or high
-
kingruedi schrieb:
Du müsstest jetzt eigentlich begründen, dass Templates moderner sind als Generics, nicht umfangreicher.
Moderner bedeutet ja etwas zeitliches
Templates sind auch sicher nicht das wichtigste an einer Sprache, wenn auch ein cooles Feature, wie zB. boost eindrucksvoll zeigt.
http://www.artima.com/intv/generics.html <-- interessantes Gespräch zwischen Bruce Eckel, Bill Venners und Anders Hejlsberg
Jo, kenn ich.
Ich mag den Typen aber nicht mehr, seit er das über checked Exceptions gesagt hat. Er hat ganz eindeutig ein paar Konzepte von Java nicht verstanden und seine Argumentation ist äußerst fadenscheinig. Das wäre jetzt aber eher was für einen eigenen Thread...EDIT: Ahja, aber siehe hier, was hat der Meister gesagt:
The big difference between C# generics and C++ templates shows up in when the type checking occurs and how the instantiation occurs. First of all, C# does the instantiation at runtime. C++ does it at compile time, or perhaps at link time. But regardless, the instantiation happens in C++ before the program runs. That's difference number one. Difference number two is C# does strong type checking when you compile the generic type. For an unconstrained type parameter, like List<T>, the only methods available on values of type T are those that are found on type Object, because those are the only methods we can generally guarantee will exist. So in C# generics, we guarantee that any operation you do on a type parameter will succeed.
-
Optimizer schrieb:
Kannst du das genauer ausführen? In C# muss man Constraints definieren, sonst kann man nur die System.Object Schnittstelle benutzen!
Es hindert mich aber nichts an einem
Base b;
((Derived)b).derived_only_method();wenn ich mal keine Constraints haben will. Natürlich kommt das mit Runtime Overhead, den ich nicht hätte, wenn ich ein Concept dafür definiert hätte. Aber es ist technisch machbar und kann ja auch mal nützlich sein (wenn ich eine spezielle Implementierung für etwas haben will).
Das muss man in C++ wohl keineswegs so machen. Ich kann eine sortierte Liste schreiben, und die T-Objekte mit '<' vergleichen, wie ich lustig bin.
Wenns dann doch nicht geht, merkt der Compiler das irgendwann. In C# ist dagegen gleich klar: Wenn ich jetzt ne SortedList<T : ICompareable> benutzen will, muss meine Klasse dieses Interface implementieren.Klar, nur muss ich es eben auch in C# nicht machen, weil ich einfach nach IComparable casten kann (wenn ich lustig bin). Genauso wie ich in C++ einen Concept Check einbauen kann.
Wie gesagt der Unterschied ist die 'Default Einstellung':
Java/C# haben defaultmäßig Concepts
C++ hat defaultmäßig keine ConceptsAch bitte, das ist doch nicht dein Ernst. Ein Sinn der Generics ist es ja, die casts zu eliminieren, wenn ich es trotzdem mach, brauch ich die Generics nicht verwenden. Kein Mensch würde so etwas tun. (Ok, Zitat Scott Meiers: "Alles was möglich ist, wird irgendwann irgendjemand tun")
Warum?
Ich kann doch sagen: Um meine List zu verwenden muss das Ding nicht vergleichbar sein - ausser wenn man .sort() verwendet (und ich weiss, dass man sort() normalerweise nicht braucht). Dann wäre es doof IComparable als Concept zu definieren, weil es eben nicht zwangsläufig nötig ist.Natürlich wird man das nicht oft brauchen (weil es eben nur für ausnahmen sinnvoll ist - aber bei diesen ausnahmen ist es eben _sehr_ sinnvoll) - aber es ist möglich. Und genau darauf weise ich hin.
Denn Concepts sind nicht immer nur ein Vorteil - man braucht sie manchmal nicht, bzw. behindern sie einen. Denn sobald ich sage, dass ein Typ Interface A implementieren muss, dann muss es dass - auch wenn ich die Funktionalität die dadurch entsteht nicht brauche. Das IComparable ist dabei ein super Beispiel. Denn wieoft will ich eine List wirklich sortieren? Normalerweise wesentlich seltener als eine zu verwenden, oder? Wozu muss dann mein Typ IComparable implementieren? Vielleicht macht das ja auch keinen Sinn? Ich meine, nicht jede Klasse die ich handle muss einen Operator< implementieren. zB Plugin Klassen. Ich kann zwar sagen: a!=b - das ist nicht schwer, aber welches Plugin ist kleiner? Unmöglich zu sagen. Aber dennoch möchte ich 10 Plugins in eine Liste stecken.
Wenn ich aber ints hineinstecke - dann will ich die auch sortieren können...
Nein. In Java/C# musst du sie haben. Und in C++ funktionieren sie anders. Ich habe nicht gesagt, dass C++ mehr Laufzeitfehler produziert.
Eben nicht.
In Java ist es sowieso egal ob ich caste oder der Compiler. Lediglich in C# habe ich dann einen overhead - der aber für spezielle Situationen durchaus verkraftbar ist.Oh nein!! Du solltest sie wirklich öfters benutzen. Auf Bytecode-Ebene sind sie ein Hack, das stimmt. Man wollte aus gutem Grund die VM nicht ändern.
Aber auf Hochsprachenebene sind sie ein für Java völlig neues Sprachfeature, ein sehr mächtiges. Über das "cast vor dem Programmierer verstecken" geht das weit hinaus. Constraints an Typparameter sind sogar ein ziemlich neues, eigenes Konzept.Naja, schau sie dir mal in C# an - das sind gute Generics. Klar wird es gut versteckt, aber Hack bleibt Hack. Welche möglichkeit bieten mir Java Generics denn, die ich nicht mit geringem Aufwand auch ohne Generics hinbekomme?
ein Concept Check geht ja simpel:
(ConceptA)obj;
Und sonst erspart es mir nur die Casts selber zu schreiben.
Oder übersehe ich einen wesentlichen Punkt?In C# sind Generics Native. Da habe ich auch noch die Typinformationen zur Runtime - kann also auch Generics zur Laufzeit erstellen usw. Da hat man wirklich neue Möglichkeiten.
Concepts sind aber nicht wirklich so neu - oder können Java Concepts etwas, dass ich nicht kenne? Concepts sind doch ein einfaches "Pattern Matching" ala "Typ X implementiert Interface A und erbt von Klasse B".
Concepts in C++ sind da sogar noch etwas weiter - indem eben nicht nur Typen gecheckt werden können, sondern sogar einzelne Funktionen.
-
Shade Of Mine schrieb:
Optimizer schrieb:
Kannst du das genauer ausführen? In C# muss man Constraints definieren, sonst kann man nur die System.Object Schnittstelle benutzen!
Es hindert mich aber nichts an einem
Base b;
((Derived)b).derived_only_method();wenn ich mal keine Constraints haben will. Natürlich kommt das mit Runtime Overhead, den ich nicht hätte, wenn ich ein Concept dafür definiert hätte. Aber es ist technisch machbar und kann ja auch mal nützlich sein (wenn ich eine spezielle Implementierung für etwas haben will).
Meinst du jetzt so etwas:
class Comparer<T> { static int compare(T a, T b) { return ((Comparable)a).compareTo((Compareable)b); } }
So etwas wäre Selbstmord. Es kann dir ja niemand garantieren, dass T Compareable ist. Nein, so etwas würde man nicht machen, es ist nicht nützlich und dafür gibt es auch nie einen guten Grund. Es gibt ja nicht zum Spaß die Constraints und ich könnte die Anforderung an den Typ T stellen, dieses Interface zu implementieren.
Spezialisierung kann ich anders viel besser erreichen:class NumberComparer extends Comparer<Number> {}
Klar, nur muss ich es eben auch in C# nicht machen, weil ich einfach nach IComparable casten kann (wenn ich lustig bin).
Das ist kein Vergleich. Beim casten entlässt man immer den Compiler aus der Verantwortung. Ich betone es nochmal: So etwas würde man wie in dem Beispiel oben niemals machen. Es gibt nicht den geringsten Grund dafür.
Das hat mit default-Einstellung nichts zu tun. Innerhalb eines generischen Typs, wo ich alle Anforderungen, die ich haben will, definieren kann, ist casten vorsätzliche Sabotage. Jeder vernünftige Compiler warnt davor.Warum?
Ich kann doch sagen: Um meine List zu verwenden muss das Ding nicht vergleichbar sein - ausser wenn man .sort() verwendet (und ich weiss, dass man sort() normalerweise nicht braucht). Dann wäre es doof IComparable als Concept zu definieren, weil es eben nicht zwangsläufig nötig ist.Nein. Man würde eine Klasse List<T> schreiben und davon eine Klassen SortableList<T extends ICompareable> ableiten. Casten ist definitiv tabu. Das ist ja gerade der Witz.
Wie ich jetzt z.B. auf Strings spezialisieren könnte, steht oben.Natürlich wird man das nicht oft brauchen (weil es eben nur für ausnahmen sinnvoll ist - aber bei diesen ausnahmen ist es eben _sehr_ sinnvoll) - aber es ist möglich. Und genau darauf weise ich hin.
Denn Concepts sind nicht immer nur ein Vorteil - man braucht sie manchmal nicht, bzw. behindern sie einen. Denn sobald ich sage, dass ein Typ Interface A implementieren muss, dann muss es dass - auch wenn ich die Funktionalität die dadurch entsteht nicht brauche. Das IComparable ist dabei ein super Beispiel. Denn wieoft will ich eine List wirklich sortieren? Normalerweise wesentlich seltener als eine zu verwenden, oder? Wozu muss dann mein Typ IComparable implementieren? Vielleicht macht das ja auch keinen Sinn? Ich meine, nicht jede Klasse die ich handle muss einen Operator< implementieren.
Ja. Es kann mit Constraints etwas umständlicher sein. Ist mir auch einmal schon passiert. Constraints haben nicht nur Vorteile. Vollkommen klar. Ich will auch nur sagen, dass es ein anderes Konzept ist.
Ich persönlich finde es schöner, du kannst natürlich völlig anderer Meinung sein. Allerdings ist es btw. nicht wirklich ein Aufwand, jetzt von der List<T> abzuleiten und noch eine sort-Methode dazuzudefinieren. Das muss man schon auch mal sehen.Naja, schau sie dir mal in C# an - das sind gute Generics. Klar wird es gut versteckt, aber Hack bleibt Hack. Welche möglichkeit bieten mir Java Generics denn, die ich nicht mit geringem Aufwand auch ohne Generics hinbekomme?
ein Concept Check geht ja simpel:
(ConceptA)obj;
Und sonst erspart es mir nur die Casts selber zu schreiben.
Oder übersehe ich einen wesentlichen Punkt?In C# sind Generics Native. Da habe ich auch noch die Typinformationen zur Runtime - kann also auch Generics zur Laufzeit erstellen usw. Da hat man wirklich neue Möglichkeiten.
Concepts sind aber nicht wirklich so neu - oder können Java Concepts etwas, dass ich nicht kenne? Concepts sind doch ein einfaches "Pattern Matching" ala "Typ X implementiert Interface A und erbt von Klasse B".
Concepts in C++ sind da sogar noch etwas weiter - indem eben nicht nur Typen gecheckt werden können, sondern sogar einzelne Funktionen.Ok. Dann sind sie in Java so neu eben doch nicht. Das mit den Typinformationen ist leider schade, insofern bietet C# da tatsächlich eher was neues.
Das mit dem "Concepts auf Methodenebene in C++" ist aber ein ziemlich schlechter Witz. Der C++ Compiler probiert richtig dumm aus, ob es geht oder nicht. Wie ein Spielkind.
Ich kann auch für jede Methode ein Interface anbieten. Aber das ist natürlich nicht der Sinn der Sache und ich würde darin keinen Vorteil sehen. Das man in C++ keine Interfaces implementieren muss, kann eher noch ein zusätzliches Problem sein. C++ hat ja durchaus die Tendenz, ein bisschen mehrdeutig zu sein. Vielleicht meckert der Compiler gar nicht, weil er implizit mein Foo in ein Bar umwandeln kann (vielleicht hat er nen Konstruktor gefunden) und compiliert dann was zusammen, was ich nicht beabsichtigt habe.
-
Optimizer schrieb:
So etwas wäre Selbstmord.
Warum? Fliegt doch bloss eine BadCastException oder wie sie auch immer heissen mag.
Es kann dir ja niemand garantieren, dass T Compareable ist.
Wozu auch? Wenn jemand sort() verwenden will, muss T Comparable sein. Ist T das nicht, dann geht sort() nicht und man hat ohne auszukommen.
Nein, so etwas würde man nicht machen, es ist nicht nützlich und dafür gibt es auch nie einen guten Grund. Es gibt ja nicht zum Spaß die Constraints und ich könnte die Anforderung an den Typ T stellen, dieses Interface zu implementieren.
Wie würdest du mein imiginäres Problem mit Plugin und List lösen?
Das ist kein Vergleich. Beim casten entlässt man immer den Compiler aus der Verantwortung.
Das will ich ja. Ich will ja eben die Beschränkung umgehen.
Ich betone es nochmal: So etwas würde man wie in dem Beispiel oben niemals machen. Es gibt nicht den geringsten Grund dafür.
Warum?
Ich würde es machen.Das hat mit default-Einstellung nichts zu tun. Innerhalb eines generischen Typs, wo ich alle Anforderungen, die ich haben will, definieren kann, ist casten vorsätzliche Sabotage. Jeder vernünftige Compiler warnt davor.
Warum? Ich will ja eben _nicht_ alle Anforderungen festlegen - weil ich optionale Komponenten habe.
Nein. Man würde eine Klasse List<T> schreiben und davon eine Klassen SortableList<T extends ICompareable> ableiten. Casten ist definitiv tabu. Das ist ja gerade der Witz.
Wie ich jetzt z.B. auf Strings spezialisieren könnte, steht oben.Und was machen wir, wenn ich für .sort() IComparable brauche und für .clone() eine .clone() Methode? Habe ich dann
List, SortableListe, CloneableList, SortableCloneableList?Ich will mich aber nicht auf ein Beispiel fixieren.
Ja. Es kann mit Constraints etwas umständlicher sein. Ist mir auch einmal schon passiert. Constraints haben nicht nur Vorteile. Vollkommen klar. Ich will auch nur sagen, dass es ein anderes Konzept ist.
Anders als was? Als in C++? Ne, das sehe ich nicht.
Ich persönlich finde es schöner, du kannst natürlich völlig anderer Meinung sein. Allerdings ist es btw. nicht wirklich ein Aufwand, jetzt von der List<T> abzuleiten und noch eine sort-Methode dazuzudefinieren. Das muss man schon auch mal sehen.
Ja, das ist ein Problem. Denn wie mache ich eine SortableClonableList?
Ok. Dann sind sie in Java so neu eben doch nicht. Das mit den Typinformationen ist leider schade, insofern bietet C# da tatsächlich eher was neues.
Das mit dem "Concepts auf Methodenebene in C++" ist aber ein ziemlich schlechter Witz. Der C++ Compiler probiert richtig dumm aus, ob es geht oder nicht. Wie ein Spielkind.Na und? Soll er probieren. In Java macht er es doch genauso. Er castet und schaut ob es gut geht. Oder er schaut direkt nach - ändert auch nicht viel. Natürlich ist 'nachschauen' schöner als 'ausprobieren' aber das Resultat ist das selbe. (Anders als bei der Implementierung von Generics in Java - die eben das selbe Resultat liefern wie wenn der User selber castet -> im Gegensatz zu C# wo das Resultat etwas anderes ist).
Ich kann auch für jede Methode ein Interface anbieten.
Jein, leider nicht so einfach.
Denn eine Klasse muss das Interface implementieren. Man kann also nicht nachträglich ein Interface einfügen. Somit kann ich nicht ein eigenes Interface für einen Container schreiben - weil ich somit alle Objekte ändern müsste die ich je in den Container reingeben will. Das ist etwas doof.Das man in C++ keine Interfaces implementieren muss, kann eher noch ein zusätzliches Problem sein. C++ hat ja durchaus die Tendenz, ein bisschen mehrdeutig zu sein. Vielleicht meckert der Compiler gar nicht, weil er implizit mein Foo in ein Bar umwandeln kann (vielleicht hat er nen Konstruktor gefunden) und compiliert dann was zusammen, was ich nicht beabsichtigt habe.
Das hat nichts mit Interfaces zu tun, sondern mit impliziter Typumwandlung. Einfach alle Ctors explicit machen und das Problem ist größtenteils behoben. Allerdings sehe ich den Zusammenhang mit impliziter Typumwandlung hier nicht...
-
Shade Of Mine schrieb:
Optimizer schrieb:
So etwas wäre Selbstmord.
Warum? Fliegt doch bloss eine BadCastException oder wie sie auch immer heissen mag.
Genau. Compile-Zeit-Fehler grundlos auf Laufzeitfehler verlagern, das ham ma gern.
Es kann dir ja niemand garantieren, dass T Compareable ist.
Wozu auch? Wenn jemand sort() verwenden will, muss T Comparable sein. Ist T das nicht, dann geht sort() nicht und man hat ohne auszukommen.
Ja aber das ist doch genau der Blödsinn. Wofür hab ich dann constraints?
Wie würdest du mein imiginäres Problem mit Plugin und List lösen? "Ich meine, nicht jede Klasse die ich handle muss einen Operator< implementieren. zB Plugin Klassen. Ich kann zwar sagen: a!=b - das ist nicht schwer, aber welches Plugin ist kleiner? Unmöglich zu sagen. Aber dennoch möchte ich 10 Plugins in eine Liste stecken."
Das ist doch kein Problem. Ein UniqueList müsste beispielsweise die Elemente auf Gleichheit prüfen. Wenn man jetzt mal außer Acht lässt, dass Object equals() mitbringt, müsste ich halt eine Interface ImplementsEquals verlangen für meine UniqueList. Ich weiß, dir kommt das umständlich und unschön vor.
Aber es ist nicht wirklich ein Problem, vor allem natürlich bei equals() jetzt nicht.Das will ich ja. Ich will ja eben die Beschränkung umgehen.
Wozu, wenn du die Beschränkung selber definieren kannst? Das ist wie, wenn ich ne Variable final mach und sie dann doch h4x0r. Dann mach ich sie halt nicht final.
Warum?
Ich würde es machen.Dann würde ich deine Liste nicht kaufen.
Der Compiler warnt ja auch nicht zum Spaß. Der Punkt ist, dass die generische Klasse für sich alleine steht. Sie wird nicht zur Kompilierzeit instanziert. Sie steht alleine und weiß über T nur das, was du sagst. Deshalb sind casts sinnlos.
Warum? Ich will ja eben _nicht_ alle Anforderungen festlegen - weil ich optionale Komponenten habe.
Dann extende oder spezialisiere die Liste entsprechend. Wo ist denn das Problem?
Und was machen wir, wenn ich für .sort() IComparable brauche und für .clone() eine .clone() Methode? Habe ich dann
List, SortableListe, CloneableList, SortableCloneableList?Kommt in der Praxis so nicht vor. Guckst du ins Java API, da findest du Lists, Sets und Maps und allerlei Spezialisierungen davon. Wenn du jetzt ne List sortierst, gibt es in der Klasse Collections dafür ne generische Methode.
Der übergibst du entweder nen Comparer<T> oder du rufst die ohne Comparer auf und implementierst dafür Compareable. Ein Set fordert halt, dass du equals() redefiniert hast (oder du übergibst wieder nen Comparer<T>). Glaub mir, es ist schon zu benutzen.
Ich verstehe die Grundsatzfrage zwar, aber das kommt so nicht vor, wie du es schilderst.Ja. Es kann mit Constraints etwas umständlicher sein. Ist mir auch einmal schon passiert. Constraints haben nicht nur Vorteile. Vollkommen klar. Ich will auch nur sagen, dass es ein anderes Konzept ist.
Anders als was? Als in C++? Ne, das sehe ich nicht.
Ich schon. Ich finde auch den Link von kingruedi recht aufschlussreich in der Hinsicht.
Ja, das ist ein Problem. Denn wie mache ich eine SortableClonableList?
class SortableCloneableList<T extends Compareable, Cloneable>
Natürlich ist 'nachschauen' schöner als 'ausprobieren' aber das Resultat ist das selbe. (Anders als bei der Implementierung von Generics in Java - die eben das selbe Resultat liefern wie wenn der User selber castet -> im Gegensatz zu C# wo das Resultat etwas anderes ist).
Nein, bitte sieh es doch ein, dass es C# und Java gleich ist. Das muss dich gar nicht interessieren, dass intern gecastet wird. Der cast geht nie schief, weil der Compiler es schon prüft. Er "schaut" nach, ob dein new Integer() Cloneable und Compareable implementiert und reagiert entsprechend. Der Cast im Bytecode ist vollkommen bedeutungslos und existiert nur aus Kompatibilitätsgründen.
ein, leider nicht so einfach.
Denn eine Klasse muss das Interface implementieren. Man kann also nicht nachträglich ein Interface einfügen. Somit kann ich nicht ein eigenes Interface für einen Container schreiben - weil ich somit alle Objekte ändern müsste die ich je in den Container reingeben will. Das ist etwas doof.Das ist halt ein strengeres System. Der Gedanke ist interessant, aber das hat mir noch keinen Ärger bereitet.
Das hat nichts mit Interfaces zu tun, sondern mit impliziter Typumwandlung. Einfach alle Ctors explicit machen und das Problem ist größtenteils behoben. Allerdings sehe ich den Zusammenhang mit impliziter Typumwandlung hier nicht...
Das sollte ein Beispiel sein. Bei Java gehts nicht, wenn ich Cloneable nicht implementiere. Bei C++ könnte es "zufällig" gehen, z.B. wegen impliziter Typumwandlung. Und das Template checkts nicht und generiert Code, den ich nicht wollte.
-
Constraints an Typparameter sind sogar ein ziemlich neues, eigenes Konzept.
Aha, wann gabs sowas denn des erste mal? Irgendwann in den 80ern. Sooo neu ist das auch wieder nicht.
Kannst du das genauer ausführen? In C# muss man Constraints definieren, sonst kann man nur die System.Object Schnittstelle benutzen!
Das muss man in C++ wohl keineswegs so machen. Ich kann eine sortierte Liste schreiben, und die T-Objekte mit '<' vergleichen, wie ich lustig bin.
Wenns dann doch nicht geht, merkt der Compiler das irgendwann. In C# ist dagegen gleich klar: Wenn ich jetzt ne SortedList<T : ICompareable> benutzen will, muss meine Klasse dieses Interface implementieren.Beide Lösungen sind beschissen.
Java/C#-artige Interfaces an sich sind schonmal richtiger Schrott. Wenn, dann will ich was, was mir Freiheiten einräumt und nicht verkappte ABCs (Abstrakte Basisklassen). Und dass ordentliche Interfaces nicht so schwer sein können, zeigen Sprachen, wie Nice mit seinen "abstract interfaces", Heron mit seinen Interfaces und die hoffentlich bald fertig gestellte BIL (Boost Interface Library) für C++ (auch wenn hier Support seitens der Sprache natürlich toll wäre).
Interfaces sind für mich etwas, das auf strukturellem Subtypeing basieren sollte. Es geht um's Konzept, die zur Verfügung stehende Schnittstelle. Was da jetzt geerbt/"implementiert" wurde ist da vollkommen egal. Nominative Vererbung ist an anderer Stelle sinnvoll, aber nicht hier.Naja, ein wenig vom Thema abgekommen.
Stoustrup hat doch einen Proposal zum Thema Constrains veröffentlich. Das Konzept ist vielleicht auch noch nicht perfekt, aber doch deutlich besser, als das, was Java/C# da machen.
Man muss sich doch nurmal "Klassen" (keine OO-Klassen) in Haskell ansehen, um zu sehen, wie man Typen beschränken kann.
-
Helium schrieb:
Constraints an Typparameter sind sogar ein ziemlich neues, eigenes Konzept.
Aha, wann gabs sowas denn des erste mal? Irgendwann in den 80ern. Sooo neu ist das auch wieder nicht.
Danke, dass du mich auch noch einmal darauf hinweist.
Mir fällt gerade auf, dass ich meine Trumpfkarte noch gar nicht ausgespielt habe:void sortiereNachBetragDerZahlen(List<? extends Number> list) { // frisst List<Number>, List<Float>, List<Integer>, ... // bei *voller* *statischer* Typsicherheit kann ich mit der list alles // machen, außer natürlich Objekte hinzufügen... }
Gibt's das auch schon länger? Mit C++ Templates geht das jedenfalls nicht.
Dann gibt's noch das hier:
List<? super Integer> list; list = new List<Integer>(); list = new List<Number>(); ... // Ich kann in die Liste immer ein Integer-Objekt hinzufügen, auslesen aber nicht
Und Bivarianz:
void funktion(List<?> list); // Nimmt alle Listen
@Shade: Ich denke schon, dass solche Möglichkeiten die Bezeichnung "neues Sprachfeature" rechtfertigen und das es mehr ist als nur ein Bytecode-Hack.
-
Optimizer schrieb:
Gibt's das auch schon länger? Mit C++ Templates geht das jedenfalls nicht.
Klar. Einfach ein Concept dafür schreiben. Schliesslich kann man ja recht einfach checken ob eine Klasse von einer anderen abgeleitet ist.
und man kann der Liste dann auch elemente hinzufügen wenn man will
Da man den genauen Typ trotzdem kennt.
Dann gibt's noch das hier:
List<? super Integer> list; list = new List<Integer>(); list = new List<Number>(); ... // Ich kann in die Liste immer ein Integer-Objekt hinzufügen, auslesen aber nicht
Geht so in C++ natürlich nicht, weil List<Int> und List<Num> 2 verschiedene Typen sind. Lässt sich aber ein Wrapper schreiben und dann geht es auch.
Und Bivarianz:
void funktion(List<?> list); // Nimmt alle Listen
??
das ist wohl das elementarste was es gibt...@Shade: Ich denke schon, dass solche Möglichkeiten die Bezeichnung "neues Sprachfeature" rechtfertigen und das es mehr ist als nur ein Bytecode-Hack.
Na dann erklär mir mal die Unterschiede zwischen Java Generics und selber casten. Ich sehe da nur etwas Schreibaufwand und OK: man kann einige Fehler schon zur Compiletime wissen. Aber ist das jetzt so neu??
-
Ich sehe da nur etwas Schreibaufwand und OK: man kann einige Fehler schon zur Compiletime wissen.
lol? Ist das vielleicht gar nichts? Ich kann auf generischen Typen (z.B. Listen) arbeiten. Ich kann (mehrfache) Type-Bounds vorgeben. Ich kann mehrdeutige Typebounds vorgeben. Ich kann generische Typen polymorph behandeln.
Davon können C++ Templates bei weitem nicht alles. Ein Wrapperklasse zu schreiben ist bei praktisch unendlich vielen möglichen abgeleiteten Klassen keine Alternative, vor allem auch, weil du den Wrapper immer wieder anpassen musst, wenn du eine neue Klasse definierst.Und alles bei statischer Typsicherheit. Das ist wohl gar nichts. Wie du immer auf den Bytecode-Cast schaust... der ist nur Zierde. Den braucht man für Generics nicht, nur für die VM.
Seit wann muss ein Sprachfeature neue Elemente zum Bytecode hinzufügen? Selbstverständlich ist das ein Sprachfeature und kein Hack. Wieso willst du das nicht anerkennen?
Du stellst immer eine Verbindung zwischen Generics und casten her, das ist aber falsch! Generics haben mit casten nichts zu tun. Ich kann in C++ natürlich auch so krasse Sachen nutzen. Über void* Pointer geht alles.
Das zählt genausowenig, wie in Java zu casten, das ist nicht das selbe.
-
Optimizer schrieb:
Ich kann (mehrfache) Type-Bounds vorgeben. Ich kann mehrdeutige Typebounds vorgeben. Ich kann generische Typen polymorph behandeln.
Und was davon kann ich in C++ nicht?
OK, die Konzepte sind aufwendiger zu implementieren.
Dafür kann ich ohne Probleme ein C++ Template schreiben welches du nie in Java implementieren kannstJava verlangt halt immer ein Interface - wenn ich das in C++ genauso machen würde, wären die Concept checks auch viel simpler (weil ich nur noch die Vererbungshierachie checken müsste).
Ich sehe das übrigens ähnlich wie Helium (nur nicht ganz so extrem). Dadurch dass ich dauernd ein Interface brauche - und es nicht reicht, dass die Klasse Run() anbietet, sondern auch noch von IRunable erben muss, ist es IMHO ziemlich lästig zu arbeiten. Aber OK - in C++ habe ich eben die möglichkeit IRunable zu implementieren oder Run() ganz unabhängig davon zu implementieren. Diese flexibilität macht die Concept Checks natürlich komplizierter, hat aber den Vorteil, dass ein Interface unabhängig von Interface-Klassen sind.
Ein Wrapperklasse zu schreiben ist bei praktisch unendlich vielen möglichen abgeleiteten Klassen keine Alternative, vor allem auch, weil du den Wrapper immer wieder anpassen musst, wenn du eine neue Klasse definierst.
Nein. Der Wrapper ist selber ein Template - damit muss ich ihn nicht öfters schreiben.
Allerdings reicht 1 Wrapper nicht, da man die List selber nochmal wrappen muss: man braucht also 3 Klassen mit zusammen etwa 30-40 Zeilen. Tut also nicht wirklich weh.Seit wann muss ein Sprachfeature neue Elemente zum Bytecode hinzufügen? Selbstverständlich ist das ein Sprachfeature und kein Hack. Wieso willst du das nicht anerkennen?
OK, dann sage ich eben: Generics in Java nicht nicht konsequent durchgezogen.
Du stellst immer eine Verbindung zwischen Generics und casten her, das ist aber falsch! Generics haben mit casten nichts zu tun. Ich kann in C++ natürlich auch so krasse Sachen nutzen. Über void* Pointer geht alles.
Das zählt genausowenig, wie in Java zu casten, das ist nicht das selbe.Wie arbeiten denn die Generics? Sie machen die casts statt dem User, oder? Man hat keine Typinformationen mehr zur Laufzeit, man muss primitive Typen boxen um sie in templates zu verwenden und das perverseste:
T foo=new T();
geht nicht - weil T zur Laufzeit nicht bekannt ist. Sowas finde ich sehr unintuitive und ist ein klares Tribut dass man dem "Bytecode nicht ändern" zahlt. Insofern hat der Bytecode damit schon etwas zu tun - denn würde man Bytecode-Kompatibilität aufgeben, würden Generics anders aussehen (nämlich so wie in C#)OK, nennen wir es nicht Hack - aber tatsache bleibt, dass die Generics in Java mager sind. C# zeigt, wie man es richtig machen kann. Hier sind Generics Native - was man daran erkennt, dass man Generics zur Runtime genauso behandeln kann wie zur Compiletime. Von der Performance mal abgesehen
-
T foo=new T();
geht nicht - weil T zur Laufzeit nicht bekannt ist.Vermisst du das wirklich so sehr? Ich habe es noch nie gebraucht und ich programmiere jetzt doch schon längere Zeit mit der 2005 Beta.
In diesem Zusammenhang ist übrigens interessant, dass man sich dagegen entschieden hat, noch weitere mögliche constraints als den Standardkonstruktor aufzunehmen.
Wahrscheinlich gibt's das also wirklich nur, damit man einen Standardkonstruktor für Valuetypes in Arrays voraussetzen kann. Ein "Manko", an dem Java nicht leidet, weil die Valuetypes nicht in den Generics nutzbar sind. Rein OO betrachtet ist damit die Java-Lösung (ein Integer anstatt ein int) schöner. Aber natürlich nicht sehr performant.Sowas finde ich sehr unintuitive und ist ein klares Tribut dass man dem "Bytecode nicht ändern" zahlt. Insofern hat der Bytecode damit schon etwas zu tun - denn würde man Bytecode-Kompatibilität aufgeben, würden Generics anders aussehen (nämlich so wie in C#)
Klar. Wir müssen ja keine Sekunde darüber diskutieren, dass Generics nicht so toll implementiert sind.
Darum geht es mir ja auch gar nicht. Aber es hat sich vorhin so angehört, als würdest du von der schlechten Implementierung, die darin besteht, casts einzusetzen, darauf schließen, dass es kein eigenes Sprachfeature ist, sondern nur ein versteckter Cast.
Das ist aber natürlich Unsinn. Dann wären C++ Templates auch nur ein Hack und zwar noch ein viel einfacherer, weil der Compiler nur dumm ausprobiert. Selbstverständlich sind die Templates aber ein tolles und mächtiges Sprachfeature.Offtopic:
Mir gefallen die Generics btw. in C# auch besser. C# ist ein fantastisches Beispiel für "besser gut geklaut als schlecht selber gemacht" - in vielerlei Hinsicht. In Java konnte man so weit aber nicht gehen.
Es sind schon viel zu viele hunderte Millionen Zeilen Quellcode in Java geschrieben worden. C# ist erst dabei, sich zu etablieren.
Ich halte es aber nicht für unmöglich, dass sich das in Java auch mal ändern wird.
Man muss zu guter Letzt auch feststellen, dass sich die Situation durch die Generics keineswegs verschlechtert hat. Das wird zu oft übersehen.
-
OK, kannst du nochmal genau darauf eingehen, wie es mit co- und contra-Varianz in Java aussieht?
Kann ich sagen, dass Foo<T> ein Subtyp von Foo<S> ist, wenn T ein Subtyp von S ist (so wie es bei Arrays ist)?
Wenn ja, was passiert, wenn ich einen covarianten Typ in einer contravarianten position verwende? Also ich kenne das jetzt z.B. von Scala:class Array[+a] { def apply(index: int): a; def update(index: int, elem: a): unit; // ^ covariant typparameter taucht an contravarianter Stelle auf }
Das plus vor dem a bedeutet, dass was ich oben beschrieben habe.
-
Kann ich sagen, dass Foo<T> ein Subtyp von Foo<S> ist, wenn T ein Subtyp von S ist (so wie es bei Arrays ist)?
Nein, so geht es nicht.
Die generischen Typen sind zunächst mal invariant, d.h. es geht nurFoo<T> foo = new Foo<T>();
Weil das ein wenig spröde ist hat man nen bivarianten wildcard eingeführt.
Foo<?> foo = new Foo<IRGENDWAS>();
Dort kann ich aber natürlich nichts machen, wofür man irgendwie Informationen über den parametrisierten Typ braucht.
List<?> foo; foo.size();
geht natürlich.
Deshalb kann man typebounds definieren, z.B. Kovariant:
List<? extends Number> list = new List<Integer>();
zu beachten ist hier, dass
List<Number> list = new List<Integer>();
nicht geht.
Hier kann ich beliebig Number-Objekte aus list auslesen, aber keine hinzufügen. Ich könnte ja sonst ein Integer hinzufügen, obwohl die Liste Doubles will.
Das ist das Problem bei den Arrays von Java und C#. Sie sind kovariant, erlauben aber Schreibzugriff trotzdem - eine böse Lücke im statischen Typsystem.Und dann gibt's noch kontravarianz (mit super), wo man nur reinschreiben, aber nicht auslesen kann.
Wenn ja, was passiert, wenn ich einen covarianten Typ in einer contravarianten position verwende?
Der Compiler weist alles zurück, was nicht 100%ig gut gehen kann. Die einzelnen Einschränkungen stehen oben für die 4 möglichen Fälle.
-
Danke