Was sind die Vorteile der Löschung von Java-Typen?


98

Ich habe heute einen Tweet gelesen , der sagte:

Es ist lustig, wenn sich Java-Benutzer über das Löschen von Typen beschweren, was das einzige ist, was Java richtig gemacht hat, während sie alle Dinge ignorieren, die falsch gemacht wurden.

Meine Frage lautet also:

Gibt es Vorteile von Javas Typlöschung? Welche technischen oder programmtechnischen Vorteile bietet es (möglicherweise) außer der Präferenz für JVM-Implementierungen hinsichtlich Abwärtskompatibilität und Laufzeitleistung?


6
Wenn Sie nicht möchten, dass Ihre Frage als "meinungsbasiert" geschlossen wird, sollten Sie sie anders stellen. (Wie "Was sind die technischen Vorteile der Typlöschung"). Besser noch, fragen Sie den Hochtöner, was er wirklich sagt.
Stephen C

3
Entfernen Sie das "Warum ist es eine gute Sache". Die „gute Sache“ ist eine offensichtlich subjektive Charakterisierung und es (natürlich) wirft die Frage auf, dass Typ Löschung ist eine gute Sache ... die strittigen ist.
Stephen C

3
Gott helfe uns, wenn diese Seite in Fragen über alles ausarten wird, was jemals auf Twitter gepostet wurde. Nennen Sie zumindest eine seriöse Quelle für Ihre Frage.
Marquis von Lorne

21
Warum muss die Frage aus einer "seriösen Quelle" stammen? Das ist einfach albern.
Vertti

4
Nun, die meisten Probleme hier sind nicht aus JEDER Quelle, außer dem Problem der Fragesteller mit bestimmter Software / Sprache / so weiter, und glücklicherweise gibt es viele Fachleute, die ihre Zeit damit "verschwenden", diesen Menschen zu helfen.
Vertti

Antworten:


90

Typ Löschung ist gut

Bleiben wir bei den Fakten

Viele der bisherigen Antworten betreffen den Twitter-Nutzer übermäßig. Es ist hilfreich, sich auf die Nachrichten und nicht auf den Messenger zu konzentrieren. Selbst mit den bisher genannten Auszügen gibt es eine ziemlich konsistente Botschaft:

Es ist lustig, wenn sich Java-Benutzer über das Löschen von Typen beschweren, was das einzige ist, was Java richtig gemacht hat, während sie alle Dinge ignorieren, die falsch gemacht wurden.

Ich bekomme enorme Vorteile (z. B. Parametrizität) und keine Kosten (angebliche Kosten sind eine Grenze der Vorstellungskraft).

neues T ist ein kaputtes Programm. Es ist isomorph zu der Behauptung "Alle Aussagen sind wahr". Ich bin nicht groß darin.

Ein Ziel: vernünftige Programme

Diese Tweets spiegeln eine Perspektive wider, die nicht daran interessiert ist, ob wir die Maschine dazu bringen können, etwas zu tun , sondern vielmehr, ob wir begründen können, dass die Maschine etwas tut, was wir tatsächlich wollen. Gute Argumentation ist ein Beweis. Beweise können in formaler Notation oder etwas weniger Formalem angegeben werden. Unabhängig von der Spezifikationssprache müssen sie klar und streng sein. Informelle Spezifikationen sind nicht unmöglich korrekt zu strukturieren, weisen jedoch häufig Fehler in der praktischen Programmierung auf. Wir haben am Ende Abhilfemaßnahmen wie automatisierte und explorative Tests, um die Probleme auszugleichen, die wir mit informellen Überlegungen haben. Dies bedeutet nicht, dass das Testen an sich eine schlechte Idee ist, aber der zitierte Twitter-Benutzer schlägt vor, dass es einen viel besseren Weg gibt.

Unser Ziel ist es daher, korrekte Programme zu haben, über die wir klar und konsequent in einer Weise nachdenken können, die der tatsächlichen Ausführung des Programms durch die Maschine entspricht. Dies ist jedoch nicht das einzige Ziel. Wir wollen auch, dass unsere Logik ein gewisses Maß an Ausdruckskraft hat. Zum Beispiel gibt es nur so viel, was wir mit Aussagenlogik ausdrücken können. Es ist schön, eine universelle (∀) und existenzielle (∃) Quantifizierung aus so etwas wie Logik erster Ordnung zu haben.

Verwenden von Typensystemen zum Denken

Diese Ziele können von Typsystemen sehr gut angesprochen werden. Dies ist besonders deutlich aufgrund der Curry-Howard-Korrespondenz . Diese Entsprechung wird oft mit folgender Analogie ausgedrückt: Typen beziehen sich auf Programme wie Theoreme auf Beweise.

Diese Entsprechung ist etwas tiefgreifend. Wir können logische Ausdrücke nehmen und sie durch die Entsprechung zu Typen übersetzen. Wenn wir dann ein Programm mit derselben Typensignatur haben, die kompiliert wird, haben wir bewiesen, dass der logische Ausdruck universell wahr ist (eine Tautologie). Dies liegt daran, dass die Korrespondenz in beide Richtungen erfolgt. Die Transformation zwischen der Typ- / Programm- und der Theorem- / Beweiswelt ist mechanisch und kann in vielen Fällen automatisiert werden.

Curry-Howard spielt gut mit dem, was wir mit Spezifikationen für ein Programm machen möchten.

Sind Typsysteme in Java nützlich?

Selbst mit einem Verständnis von Curry-Howard fällt es manchen Menschen leicht, den Wert eines Typsystems zu verwerfen, wenn dies der Fall ist

  1. ist extrem schwer zu bearbeiten
  2. entspricht (durch Curry-Howard) einer Logik mit begrenzter Ausdruckskraft
  3. ist kaputt (was zur Charakterisierung von Systemen als "schwach" oder "stark" führt).

In Bezug auf den ersten Punkt machen IDEs das Typensystem von Java möglicherweise einfach genug, um damit zu arbeiten (das ist sehr subjektiv).

In Bezug auf den zweiten Punkt entspricht Java fast einer Logik erster Ordnung. Generika verwenden das Typensystem, das der universellen Quantifizierung entspricht. Leider geben Wildcards nur einen kleinen Teil der existenziellen Quantifizierung. Aber die universelle Quantifizierung ist ein ziemlich guter Anfang. Es ist schön sagen zu können, dass Funktionen List<A>für alle möglichen Listen universell funktionieren, da A völlig frei ist. Dies führt zu dem, worüber der Twitter-Nutzer in Bezug auf "Parametrizität" spricht.

Ein oft zitiertes Papier über Parametrizität sind Philip Wadlers Theoreme kostenlos! . Das Interessante an diesem Artikel ist, dass wir allein anhand der Typensignatur einige sehr interessante Invarianten nachweisen können. Wenn wir automatisierte Tests für diese Invarianten schreiben würden, würden wir unsere Zeit sehr verschwenden. Zum Beispiel für List<A>, von der Typensignatur allein fürflatten

<A> List<A> flatten(List<List<A>> nestedLists);

das können wir begründen

flatten(nestedList.map(l -> l.map(any_function)))
     flatten(nestList).map(any_function)

Das ist ein einfaches Beispiel, und Sie können wahrscheinlich informell darüber nachdenken, aber es ist noch schöner, wenn wir solche Beweise formell kostenlos vom Typsystem erhalten und vom Compiler überprüft werden.

Nicht löschen kann zu Missbrauch führen

Aus der Perspektive der Sprachimplementierung spielen Javas Generika (die universellen Typen entsprechen) sehr stark mit der Parametrizität, die verwendet wird, um Beweise für die Funktionsweise unserer Programme zu erhalten. Dies führt zu dem dritten erwähnten Problem. All diese Beweise und Korrektheiten erfordern ein fehlerfreies Soundsystem. Java hat definitiv einige Sprachfunktionen, die es uns ermöglichen, unsere Argumentation zu zerstören. Dazu gehören unter anderem:

  • Nebenwirkungen mit einem externen System
  • Reflexion

Nicht gelöschte Generika hängen in vielerlei Hinsicht mit Reflexion zusammen. Ohne Löschung gibt es Laufzeitinformationen, die in der Implementierung enthalten sind, mit der wir unsere Algorithmen entwerfen können. Dies bedeutet, dass wir statisch gesehen nicht das vollständige Bild haben, wenn wir über Programme nachdenken. Reflexion gefährdet ernsthaft die Richtigkeit von Beweisen, über die wir statisch argumentieren. Es ist kein Zufall, dass Reflexion auch zu einer Vielzahl kniffliger Mängel führt.

Wie können nicht gelöschte Generika "nützlich" sein? Betrachten wir die im Tweet erwähnte Verwendung:

<T> T broken { return new T(); }

Was passiert, wenn T keinen Konstruktor ohne Argumente hat? In einigen Sprachen ist das, was Sie erhalten, null. Oder Sie überspringen den Nullwert und lösen direkt eine Ausnahme aus (zu der Nullwerte ohnehin zu führen scheinen). Da unsere Sprache Turing vollständig ist, ist es unmöglich zu überlegen, welche Aufrufe broken"sichere" Typen mit Konstruktoren ohne Argumente beinhalten und welche nicht. Wir haben die Gewissheit verloren, dass unser Programm universell funktioniert.

Löschen bedeutet, dass wir überlegt haben (also löschen wir)

Wenn wir also über unsere Programme nachdenken möchten, wird dringend empfohlen, keine Sprachfunktionen zu verwenden, die unsere Argumentation stark gefährden. Wenn wir das getan haben, warum nicht einfach die Typen zur Laufzeit löschen? Sie werden nicht benötigt. Wir können eine gewisse Effizienz und Einfachheit erzielen, wenn wir zufrieden sind, dass keine Casts fehlschlagen oder dass beim Aufrufen möglicherweise Methoden fehlen.

Das Löschen fördert das Denken.


7
In der Zeit, die ich brauchte, um diese Antwort zusammenzuschustern, antworteten einige andere Leute mit ähnlichen Antworten. Aber nachdem ich so viel geschrieben hatte, beschloss ich, es zu posten, anstatt es zu verwerfen.
Sukant Hajra

32
In Bezug auf das Löschen von Typen war dies ein halbherziger Versuch, eine Funktion nach Java zu bringen, ohne die Abwärtskompatibilität zu beeinträchtigen. Es ist keine Stärke. Wenn das Hauptproblem darin bestand, dass Sie versuchen könnten, ein Objekt ohne parameterlosen Konstruktor zu instanziieren, besteht die richtige Antwort darin, eine Einschränkung von "Typen mit parameterlosen Konstruktoren" zuzulassen, um ein Sprachmerkmal nicht zu lähmen.
Basic

5
Grundsätzlich, in Bezug auf Respekt, machen Sie kein zusammenhängendes Argument. Sie behaupten lediglich, dass das Löschen "lähmt", wenn der Wert von Proofs zur Kompilierungszeit als überzeugendes Werkzeug für die Argumentation über Programme völlig außer Acht gelassen wird. Darüber hinaus sind diese Vorteile völlig orthogonal zu dem krummen Pfad und der Motivation, die Java für die Implementierung von Generika verwendet hat. Diese Vorteile gelten auch für Sprachen mit einer weitaus aufschlussreicheren Implementierung der Löschung.
Sukant Hajra

44
Das hier vorgestellte Argument ist nicht gegen das Löschen, sondern gegen die Reflexion (und die Überprüfung des Laufzeit-Typs im Allgemeinen). Grundsätzlich wird behauptet, dass die Reflexion schlecht ist. Die Tatsache, dass das Löschen mit ihnen nicht gut funktioniert, ist also gut. Es ist ein gültiger Punkt für sich (obwohl viele Leute anderer Meinung wären); Im Kontext macht dies jedoch wenig Sinn, da Java bereits über Reflection- / Laufzeitprüfungen verfügt und diese nicht und nicht verschwinden. Ein Konstrukt zu haben, das nicht mit diesem vorhandenen, nicht veralteten Teil der Sprache spielt, ist also eine Einschränkung, egal wie Sie es betrachten.
Pavel Minaev

10
Ihre Antwort ist einfach nicht zum Thema. Sie geben eine langwierige (wenn auch für sich genommen interessante) Erklärung ein, warum das Löschen von Typen als abstraktes Konzept im Allgemeinen beim Entwurf von Typsystemen nützlich ist. Das Thema ist jedoch der Vorteil einer bestimmten Eigenschaft von Java Generics mit der Bezeichnung "Typlöschung" ", das dem von Ihnen beschriebenen Konzept nur oberflächlich ähnelt, die interessanten Invarianten nicht vollständig liefert und unangemessene Einschränkungen einführt.
Marko Topolnik

40

Typen sind ein Konstrukt, mit dem Programme so geschrieben werden, dass der Compiler die Richtigkeit eines Programms überprüfen kann. Ein Typ ist ein Satz für einen Wert - der Compiler überprüft, ob dieser Satz wahr ist.

Während der Ausführung eines Programms sollten keine Typinformationen erforderlich sein - dies wurde bereits vom Compiler überprüft. Dem Compiler sollte es freigestellt sein, diese Informationen zu verwerfen, um Optimierungen am Code vorzunehmen - ihn schneller laufen zu lassen, eine kleinere Binärdatei zu generieren usw. Das Löschen von Typparametern erleichtert dies.

Java unterbricht die statische Typisierung, indem es ermöglicht, dass Typinformationen zur Laufzeit abgefragt werden - Reflection, Instanz usw. Dies ermöglicht es Ihnen, Programme zu erstellen, die nicht statisch überprüft werden können - sie umgehen das Typsystem. Es fehlen auch Möglichkeiten zur statischen Optimierung.

Die Tatsache, dass Typparameter gelöscht werden, verhindert, dass einige Instanzen dieser falschen Programme erstellt werden. Weitere falsche Programme würden jedoch nicht zugelassen, wenn mehr Typinformationen gelöscht und die Reflexion und Instanz von Einrichtungen entfernt würden.

Das Löschen ist wichtig, um die Eigenschaft der "Parametrizität" eines Datentyps aufrechtzuerhalten. Angenommen, ich habe einen Typ "Liste", der über den Komponententyp T parametrisiert ist, dh Liste <T>. Dieser Typ ist ein Vorschlag, dass dieser Listentyp für jeden Typ T identisch funktioniert. Die Tatsache, dass T ein abstrakter, unbegrenzter Typparameter ist, bedeutet, dass wir nichts über diesen Typ wissen und daher daran gehindert werden, für spezielle Fälle von T etwas Besonderes zu tun.

Beispiel: Ich habe eine Liste xs = asList ("3"). Ich füge ein Element hinzu: xs.add ("q"). Am Ende habe ich ["3", "q"]. Da dies parametrisch ist, kann ich annehmen, dass List xs = asList (7); xs.add (8) endet mit [7,8] Ich weiß aus dem Typ, dass es nicht eine Sache für String und eine Sache für Int macht.

Außerdem weiß ich, dass die List.add-Funktion keine Werte von T aus der Luft erfinden kann. Ich weiß, dass, wenn meiner asList ("3") eine "7" hinzugefügt wird, die einzig möglichen Antworten aus den Werten "3" und "7" konstruiert werden. Es besteht keine Möglichkeit, dass der Liste eine "2" oder "z" hinzugefügt wird, da die Funktion diese nicht erstellen kann. Keiner dieser anderen Werte wäre sinnvoll hinzuzufügen, und die Parametrizität verhindert, dass diese falschen Programme erstellt werden.

Grundsätzlich verhindert das Löschen, dass die Parametrizität verletzt wird, wodurch die Möglichkeit falscher Programme ausgeschlossen wird, was das Ziel der statischen Typisierung ist.


15

(Obwohl ich hier bereits eine Antwort geschrieben habe und diese Frage zwei Jahre später erneut aufgegriffen habe, stelle ich fest, dass es eine andere, völlig andere Art der Beantwortung gibt. Daher lasse ich die vorherige Antwort intakt und füge diese hinzu.)


Es ist höchst fraglich, ob der mit Java Generics durchgeführte Prozess den Namen "Typlöschung" verdient. Da generische Typen nicht gelöscht, sondern durch ihre rohen Gegenstücke ersetzt werden, scheint eine bessere Wahl die "Typverstümmelung" zu sein.

Die Quintessenz des Löschens von Typen im allgemein verständlichen Sinne besteht darin, dass die Laufzeit gezwungen wird, innerhalb der Grenzen des statischen Typsystems zu bleiben, indem sie für die Struktur der Daten, auf die zugegriffen wird, "blind" gemacht wird. Dies gibt dem Compiler die volle Leistung und ermöglicht es ihm, Theoreme nur auf der Grundlage statischer Typen zu beweisen. Es hilft dem Programmierer auch, indem es die Freiheitsgrade des Codes einschränkt und dem einfachen Denken mehr Macht verleiht.

Javas Typlöschung erreicht dies nicht - sie lähmt den Compiler wie in diesem Beispiel:

void doStuff(List<Integer> collection) { 
}

void doStuff(List<String> collection) // ERROR: a method cannot have 
                   // overloads which only differ in type parameters

(Die beiden oben genannten Deklarationen werden nach dem Löschen in dieselbe Methodensignatur zusammengefasst.)

Auf der anderen Seite kann die Laufzeit immer noch den Typ eines Objekts und den Grund dafür untersuchen. Da jedoch die Einsicht in den wahren Typ durch Löschen beeinträchtigt wird, sind Verstöße gegen statische Typen trivial zu erreichen und schwer zu verhindern.

Um die Dinge noch komplizierter zu machen, existieren die ursprünglichen und gelöschten Typensignaturen nebeneinander und werden beim Kompilieren parallel berücksichtigt. Dies liegt daran, dass es beim gesamten Prozess nicht darum geht, Typinformationen aus der Laufzeit zu entfernen, sondern darum, ein generisches Typsystem in ein altes Rohtypsystem zu integrieren, um die Abwärtskompatibilität aufrechtzuerhalten. Dieses Juwel ist ein klassisches Beispiel:

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)

(Die Redundanz extends Objectmusste hinzugefügt werden, um die Abwärtskompatibilität der gelöschten Signatur zu gewährleisten.)

Lassen Sie uns in diesem Sinne das Zitat noch einmal betrachten:

Es ist lustig, wenn sich Java-Benutzer über das Löschen von Typen beschweren, was das einzige ist, was Java richtig gemacht hat

Was genau hat Java richtig gemacht? Ist es das Wort selbst, unabhängig von der Bedeutung? Schauen Sie sich zum Kontrast den bescheidenen intTyp an: Es wird nie eine Laufzeit-Typprüfung durchgeführt oder ist sogar möglich, und die Ausführung ist immer perfekt typsicher. So sieht das Löschen aus, wenn es richtig gemacht wird: Sie wissen nicht einmal, dass es da ist.


10

Das einzige, was ich hier überhaupt nicht sehe, ist, dass der Laufzeitpolymorphismus von OOP im Wesentlichen von der Reifizierung von Typen zur Laufzeit abhängt. Wenn eine Sprache, deren Rückgrat durch abgelehnte Typen an Ort und Stelle gehalten wird, eine wesentliche Erweiterung ihres Typensystems einführt und es auf der Typlöschung basiert, ist kognitive Dissonanz das unvermeidliche Ergebnis. Genau dies ist der Java-Community passiert. Aus diesem Grund hat das Löschen von Typen so viele Kontroversen ausgelöst, und letztendlich gibt es Pläne, es in einer zukünftigen Version von Java rückgängig zu machen . Etwas Lustiges in dieser Beschwerde von Java-Benutzern zu finden, verrät entweder ein ehrliches Missverständnis des Geistes von Java oder einen bewusst abwertenden Witz.

Die Behauptung "Löschen ist das einzige, was Java richtig gemacht hat" impliziert die Behauptung, dass "alle Sprachen, die auf dynamischem Versand gegen das Funktionsargument vom Laufzeittyp basieren, grundlegend fehlerhaft sind". Obwohl dies sicherlich eine legitime Behauptung für sich ist und sogar als gültige Kritik an allen OOP-Sprachen einschließlich Java angesehen werden kann, kann sie sich nicht als zentraler Punkt für die Bewertung und Kritik von Features im Kontext von Java , wo Laufzeitpolymorphismus auftritt, festlegen ist axiomatisch.

Zusammenfassend kann man sagen, dass "Typlöschung der richtige Weg für das Sprachdesign ist", Positionen, die die Typlöschung in Java unterstützen, einfach deshalb falsch platziert sind, weil es dafür viel, viel zu spät ist und dies bereits im historischen Moment war als Oak von Sun umarmt und in Java umbenannt wurde.



Ob die statische Typisierung selbst die richtige Richtung für die Gestaltung von Programmiersprachen ist, passt in einen viel breiteren philosophischen Kontext dessen, was unserer Meinung nach die Aktivität der Programmierung ausmacht . Eine Denkschule, die eindeutig aus der klassischen Tradition der Mathematik stammt, sieht Programme als Instanzen des einen oder anderen mathematischen Konzepts (Sätze, Funktionen usw.), aber es gibt eine ganz andere Klasse von Ansätzen, die das Programmieren als einen Weg dazu sehen Sprechen Sie mit der Maschine und erklären Sie, was wir von ihr wollen. Aus dieser Sicht ist das Programm eine dynamische, organisch wachsende Einheit, ein dramatisches Gegenteil des sorgfältig errichteten Gebäudes eines statisch typisierten Programms.

Es erscheint natürlich, die dynamischen Sprachen als einen Schritt in diese Richtung zu betrachten: Die Konsistenz des Programms ergibt sich von unten nach oben, ohne a priori- Konstranten, die es von oben nach unten auferlegen würden. Dieses Paradigma kann als ein Schritt zur Modellierung des Prozesses angesehen werden, bei dem wir Menschen durch Entwicklung und Lernen zu dem werden, was wir sind.


Dank dafür. Es ist genau die Art von tieferem Verständnis der beteiligten Philosophien, die ich für diese Frage erwartet hatte.
Daniel Langdon

Der dynamische Versand ist in keiner Weise von reifizierten Typen abhängig. Es hängt oft davon ab, was die Programmierliteratur "Tags" nennt, aber selbst dann müssen keine Tags verwendet werden. Wenn Sie eine Schnittstelle (für ein Objekt) erstellen und anonyme Instanzen dieser Schnittstelle erstellen, führen Sie einen dynamischen Versand durch, ohne dass reifizierte Typen erforderlich sind.
Sukant Hajra

@sukanthajra Das ist nur Haarspalterei. Das Tag ist die Laufzeitinformation, die Sie benötigen, um das Verhalten der Instanz aufzulösen. Es liegt außerhalb der Reichweite des statischen Typsystems, und das ist die Essenz des diskutierten Konzepts.
Marko Topolnik

Es gibt einen sehr statischen Typ, der genau für diese Argumentation entwickelt wurde und als "Summentyp" bezeichnet wird. Es wird auch als "Variantentyp" bezeichnet (in Benjamin Pierces sehr gutem Buch über Typen und Programmiersprachen). Das ist also überhaupt kein Haarspalterei. Sie können auch einen Katamorphismus / Fold / Besucher für einen vollständig taglosen Ansatz verwenden.
Sukant Hajra

Vielen Dank für die Theorie-Lektion, aber ich sehe die Relevanz nicht. Unser Thema ist das Typensystem von Java.
Marko Topolnik

9

Ein nachfolgender Beitrag desselben Benutzers in derselben Konversation:

neues T ist ein kaputtes Programm. Es ist isomorph zu der Behauptung "Alle Aussagen sind wahr". Ich bin nicht groß darin.

(Dies war eine Antwort auf eine Aussage eines anderen Benutzers, nämlich "es scheint in einigen Situationen, dass 'neues T' besser wäre", wobei die Idee ist, dass dies new T()aufgrund der Löschung des Typs unmöglich ist. (Dies ist umstritten - selbst wenn Tes unter verfügbar wäre Laufzeit, es könnte eine abstrakte Klasse oder Schnittstelle sein, oder es könnte sein Void, oder es könnte ein Konstruktor ohne Argumente fehlen, oder sein Konstruktor ohne Argumente könnte privat sein (z. B. weil es eine Singleton-Klasse sein soll) oder seine Der Konstruktor no-arg könnte eine aktivierte Ausnahme angeben, die von der generischen Methode nicht abgefangen oder angegeben wird - aber das war die Voraussetzung. Unabhängig davon ist es wahr, dass Sie ohne Löschung zumindest schreiben können T.class.newInstance(), was diese Probleme behandelt.))

Diese Ansicht, dass Typen isomorph zu Aussagen sind, legt nahe, dass der Benutzer einen Hintergrund in der formalen Typentheorie hat. (S) er mag sehr wahrscheinlich keine "dynamischen Typen" oder "Laufzeit-Typen" und würde ein Java ohne Downcasts und instanceofund Reflexion und so weiter bevorzugen . (Stellen Sie sich eine Sprache wie Standard ML vor, die ein sehr reichhaltiges (statisches) Typsystem hat und deren dynamische Semantik überhaupt nicht von Typinformationen abhängt.)

Es ist übrigens zu bedenken, dass der Benutzer trollt: Während er wahrscheinlich (statisch) typisierte Sprachen aufrichtig bevorzugt, versucht er nicht aufrichtig, andere von dieser Ansicht zu überzeugen. Der Hauptzweck des ursprünglichen Tweets bestand vielmehr darin, diejenigen zu verspotten, die nicht einverstanden sind, und nachdem einige dieser nicht einverstanden waren, veröffentlichte der Benutzer Follow-up-Tweets wie "Der Grund, warum Java Typlöschung hat, ist, dass Wadler et al. Was wissen." Sie tun, im Gegensatz zu Benutzern von Java ". Leider ist es schwierig herauszufinden, was er tatsächlich denkt. Glücklicherweise bedeutet dies wahrscheinlich auch, dass dies nicht sehr wichtig ist. Menschen mit dem aktuellen Tiefe, ihre Ansichten greifen im Allgemeinen nicht zu Trollen, sind ganz dieser Inhalt frei.


2
Es wird nicht in Java kompiliert, da es
Typlöschung

1
@ MarkRotteveel: Danke; Ich habe einen Absatz hinzugefügt, um dies zu erklären (und zu kommentieren).
Ruakh

1
Nach der Mischung aus Upvotes und Downvotes zu urteilen, ist dies die umstrittenste Antwort, die ich je gepostet habe. Ich bin seltsam stolz.
Ruakh

6

Eine gute Sache ist, dass es nicht notwendig war, die JVM zu ändern, als Generika eingeführt wurden. Java implementiert Generika nur auf Compilerebene.


1
JVM ändert sich im Vergleich zu was? Vorlagen hätten auch keine JVM-Änderungen erforderlich gemacht.
Marquis von Lorne

5

Der Grund, warum das Löschen von Typen eine gute Sache ist, ist, dass die Dinge, die es unmöglich macht, schädlich sind. Das Verhindern der Überprüfung von Typargumenten zur Laufzeit erleichtert das Verstehen und Überlegen von Programmen.

Eine Beobachtung , dass ich etwas kontraintuitiv gefunden, dass , wenn Funktionssignaturen sind mehr Generika, werden sie leichter zu verstehen. Dies liegt daran, dass die Anzahl möglicher Implementierungen reduziert wird. Stellen Sie sich eine Methode mit dieser Signatur vor, von der wir irgendwie wissen, dass sie keine Nebenwirkungen hat:

public List<Integer> XXX(final List<Integer> l);

Was sind die möglichen Implementierungen dieser Funktion? Sehr viele. Sie können sehr wenig darüber sagen, was diese Funktion tut. Es könnte die Eingabeliste umkehren. Es könnte sein, dass Ints gepaart, summiert und eine Liste mit der halben Größe zurückgegeben werden. Es gibt viele andere Möglichkeiten, die man sich vorstellen kann. Betrachten Sie nun:

public <T> List<T> XXX(final List<T> l);

Wie viele Implementierungen dieser Funktion gibt es? Da die Implementierung den Typ der Elemente nicht kennen kann, kann jetzt eine große Anzahl von Implementierungen ausgeschlossen werden: Elemente können nicht kombiniert oder zur Liste hinzugefügt oder herausgefiltert werden, et al. Wir beschränken uns auf Dinge wie: Identität (keine Änderung der Liste), Löschen von Elementen oder Umkehren der Liste. Diese Funktion lässt sich allein anhand ihrer Signatur leichter begründen.

Außer… in Java kann man das Typsystem immer betrügen. Da bei der Implementierung dieser generischen Methode beispielsweise instanceofÜberprüfungen und / oder Umwandlungen für beliebige Typen verwendet werden können, kann unsere auf der Typensignatur basierende Argumentation leicht unbrauchbar werden. Die Funktion könnte den Typ der Elemente überprüfen und basierend auf dem Ergebnis eine beliebige Anzahl von Dingen ausführen. Wenn diese Laufzeit-Hacks zulässig sind, werden parametrisierte Methodensignaturen für uns viel weniger nützlich.

Wenn Java keine Typlöschung hätte ( dh, Typargumente wurden zur Laufzeit geändert ), würde dies einfach mehr argumentationsbeeinträchtigende Spielereien dieser Art ermöglichen. Im obigen Beispiel kann die Implementierung die durch die Typensignatur festgelegten Erwartungen nur verletzen, wenn die Liste mindestens ein Element enthält. Wenn es Tjedoch geändert wurde, konnte es dies auch dann tun, wenn die Liste leer war. Reifizierte Typen würden nur die (bereits sehr vielen) Möglichkeiten erhöhen, unser Verständnis des Codes zu behindern.

Durch das Löschen von Schriftarten wird die Sprache weniger "leistungsfähig". Aber einige Formen von "Macht" sind tatsächlich schädlich.


1
Es scheint, als würden Sie entweder argumentieren, dass Generika für Java-Entwickler zu komplex sind, um sie zu "verstehen und zu argumentieren", oder dass man ihnen nicht trauen kann, sich nicht in den Fuß zu schießen, wenn sie die Chance dazu haben. Letzteres scheint mit Javas Ethos synchron zu sein (ala keine vorzeichenlosen Typen usw.), aber es scheint den Entwicklern ein wenig herablassend zu sein
Basic

1
@Basic Ich muss mich sehr schlecht ausgedrückt haben, denn das habe ich überhaupt nicht gemeint. Das Problem ist nicht, dass Generika komplex sind, sondern dass Dinge wie Casting instanceofunsere Fähigkeit beeinträchtigen, darüber nachzudenken, was Code basierend auf Typen tut. Wenn Java Typargumente erneut bestätigen würde, würde dies das Problem nur verschlimmern. Durch das Löschen von Typen zur Laufzeit wird das Typsystem nützlicher.
Lachlan

@lachlan es hat für mich vollkommen Sinn gemacht, und ich glaube nicht, dass eine normale Lektüre eine Beleidigung für Java-Entwickler bedeuten würde.
Rob Grant

3

Dies ist keine direkte Antwort (OP fragte "Was sind die Vorteile", ich antworte "Was sind die Nachteile")

Im Vergleich zum System vom Typ C # ist das Löschen vom Typ Java für zwei Raesons ein echtes Problem

Sie können eine Schnittstelle nicht zweimal implementieren

In C # können Sie beide IEnumerable<T1>und IEnumerable<T2>sicher implementieren , insbesondere wenn die beiden Typen keinen gemeinsamen Vorfahren haben (dh ihr Vorfahr ist Object ).

Praktisches Beispiel: In Spring Framework können Sie nicht ApplicationListener<? extends ApplicationEvent>mehrmals implementieren . Wenn Sie unterschiedliche Verhaltensweisen Tbenötigen, müssen Sie diese testeninstanceof

Du kannst kein neues T () machen

(und Sie benötigen dazu einen Verweis auf Class)

Wie andere kommentierten, kann das Äquivalent von new T()nur durch Reflektion erfolgen, nur durch Aufrufen einer Instanz von Class<T>, wobei sichergestellt wird, dass die vom Konstruktor benötigten Parameter eingehalten werden. Mit C # können Sie dies new T() nur tun , wenn Sie sich Tauf einen parameterlosen Konstruktor beschränken. Wenn Tdiese Einschränkung nicht eingehalten wird, wird ein Kompilierungsfehler ausgelöst .

In Java werden Sie häufig gezwungen sein, Methoden zu schreiben, die wie folgt aussehen

public <T> T create(....params, Class<T> classOfT)
{

    ... whatever you do
    ... you will end up
    T = classOfT.newInstance();


    ... or more advanced reflection
    Constructor<T> parameterizedConstructorThatYouKnowAbout = classOfT.getConstructor(...,...);
}

Die Nachteile im obigen Code sind:

  • Class.newInstance funktioniert nur mit einem parameterlosen Konstruktor. Wenn keine verfügbar ist, ReflectiveOperationExceptionwird zur Laufzeit ausgelöst
  • Der reflektierte Konstruktor hebt keine Probleme zur Kompilierungszeit wie oben hervor. Wenn Sie Refactor von Ihren Argumenten austauschen, wissen Sie nur zur Laufzeit

Wenn ich der Autor von C # wäre, hätte ich die Möglichkeit eingeführt, eine oder mehrere Konstruktoreinschränkungen anzugeben, die zum Zeitpunkt der Kompilierung leicht zu überprüfen sind (daher kann ich beispielsweise einen Konstruktor mit string,stringParametern benötigen). Aber der letzte ist Spekulation


2

Ein zusätzlicher Punkt, den keine der anderen Antworten berücksichtigt zu haben scheint: Wenn Sie wirklich Generika mit Laufzeit-Typisierung benötigen, können Sie diese wie folgt selbst implementieren :

public class GenericClass<T>
{
     private Class<T> targetClass;
     public GenericClass(Class<T> targetClass)
     {
          this.targetClass = targetClass;
     }

Diese Klasse ist dann in der Lage, alle Dinge zu tun, die standardmäßig erreichbar wären, wenn Java keine Löschung verwenden würde: Sie kann neue Ts zuweisen (vorausgesetzt, sie Tverfügt über einen Konstruktor, der dem erwarteten Muster entspricht) oder Arrays von Ts Testen Sie zur Laufzeit dynamisch, ob es sich bei einem bestimmten Objekt um ein Objekt handelt, Tund ändern Sie das Verhalten in Abhängigkeit davon usw.

Beispielsweise:

     public T newT () { 
         try {
             return targetClass.newInstance(); 
         } catch(/* I forget which exceptions can be thrown here */) { ... }
     }

     private T value;
     /** @throws ClassCastException if object is not a T */
     public void setValueFromObject (Object object) {
         value = targetClass.cast(object);
     }
}

Möchte jemand den Grund für die Ablehnung erklären? Dies ist, soweit ich sehen kann, eine vollkommen gute Erklärung dafür, warum der vermeintliche Nachteil des Löschsystems vom Typ Java überhaupt keine wirkliche Einschränkung darstellt. Also, was ist das Problem?
Jules

Ich stimme zu. Nicht, dass ich die Typprüfung zur Laufzeit unterstütze, aber dieser Trick kann in den Salzminen nützlich sein.
Techtangents

3
Java ist eine Sprache der Salzminen, was dies aufgrund des Mangels an Informationen zum Laufzeittyp zu einer absoluten Notwendigkeit macht. Hoffentlich wird das Löschen von Typen in einer zukünftigen Version von Java rückgängig gemacht, was Generika zu einer viel nützlicheren Funktion macht. Derzeit ist man sich
einig,

Na klar ... Solange Ihr TargetType selbst keine Generika verwendet. Aber das scheint ziemlich einschränkend zu sein.
Basic

Es funktioniert auch mit generischen Typen. Die einzige Einschränkung besteht darin, dass Sie zum Erstellen einer Instanz einen Konstruktor mit den richtigen Argumenttypen benötigen, aber jede andere mir bekannte Sprache hat dieselbe Einschränkung, sodass ich die Einschränkung nicht sehe.
Jules

0

vermeidet das Aufblähen von c ++ - ähnlichem Code, da derselbe Code für mehrere Typen verwendet wird. Das Löschen von Typen erfordert jedoch einen virtuellen Versand, während der c ++ - Code-Bloat-Ansatz nicht virtuell versendete Generika ausführen kann


3
Die meisten dieser virtuellen Versendungen werden jedoch zur Laufzeit in statische konvertiert, sodass es nicht so schlimm ist, wie es scheint.
Piotr Kołaczkowski

1
Sehr wahr, die Verfügbarkeit eines Compilers zur Laufzeit lässt Java im Vergleich zu C ++ viele coole Dinge tun (und eine hohe Leistung erzielen).
Nekromant

Wow, lass es mich irgendwo aufschreiben. Java erreicht eine hohe Leistung im Vergleich zu CPP.
Jungle_Mole

1
@jungle_mole ja, danke. Nur wenige Menschen erkennen den Vorteil, dass zur Laufzeit ein Compiler verfügbar ist, mit dem Code nach der Profilerstellung erneut kompiliert werden kann. (oder dass die Speicherbereinigung eher groß-oh (kein Müll) ist als die naive und falsche Überzeugung, dass die Speicherbereinigung groß-oh (Müll) ist (oder dass die Speicherbereinigung zwar ein geringer Aufwand ist, dies jedoch durch die Ermöglichung von Nullkosten kompensiert) Zuweisung)). Es ist eine traurige Welt, in der Menschen blindlings annehmen, was ihre Gemeinde glaubt, und aufhören, nach ersten Prinzipien zu argumentieren.
Nekromant

0

Die meisten Antworten befassen sich mehr mit der Programmierphilosophie als mit den tatsächlichen technischen Details.

Und obwohl diese Frage älter als 5 Jahre ist, bleibt die Frage bestehen: Warum ist eine Typlöschung aus technischer Sicht wünschenswert? Am Ende ist die Antwort ziemlich einfach (auf einer höheren Ebene): https://en.wikipedia.org/wiki/Type_erasure

C ++ - Vorlagen sind zur Laufzeit nicht vorhanden. Der Compiler gibt für jeden Aufruf eine vollständig optimierte Version aus, sodass die Ausführung nicht von Typinformationen abhängt. Aber wie geht eine JIT mit verschiedenen Versionen derselben Funktion um? Wäre es nicht besser, nur eine Funktion zu haben? Ich möchte nicht, dass die JIT alle verschiedenen Versionen davon optimieren muss. Nun, aber was ist dann mit Typensicherheit? Ich denke, das muss aus dem Fenster gehen.

Aber Moment mal: Wie macht .NET das? Reflexion! Auf diese Weise müssen sie nur eine Funktion optimieren und Informationen zum Laufzeittyp abrufen. Und deshalb waren .NET-Generika früher langsamer (obwohl sie viel besser geworden sind). Ich behaupte nicht, dass das nicht bequem ist! Aber es ist teuer und sollte nicht verwendet werden, wenn es nicht unbedingt notwendig ist (es wird in dynamisch typisierten Sprachen nicht als teuer angesehen, da der Compiler / Interpreter sowieso auf Reflexion angewiesen ist).

Auf diese Weise ist die generische Programmierung mit Typlöschung nahezu kein Overhead (einige Laufzeitprüfungen / Umwandlungen sind noch erforderlich): https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

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.