Aus dem, was ich gelesen habe: Der Grund ist, dass es nicht einfach ist zu bestimmen, welche Methode tatsächlich aufgerufen wird, wenn wir eine Vererbung haben.
Warum verfügt Java jedoch nicht mindestens über eine Schwanzrekursionsoptimierung für statische Methoden und erzwingt eine ordnungsgemäße Methode zum Aufrufen statischer Methoden mit dem Compiler?
Warum unterstützt Java die Schwanzrekursion überhaupt nicht?
Ich bin mir nicht sicher, ob es hier überhaupt Schwierigkeiten gibt.
In Bezug auf das vorgeschlagene Duplikat , wie von Jörg W Mittag 1 erklärt :
- Bei der anderen Frage geht es um TCO, bei der anderen um TRE. TRE ist viel einfacher als TCO.
- Bei der anderen Frage geht es auch darum, welche Einschränkungen die JVM für Sprachimplementierungen auferlegt, die in die JVM kompiliert werden sollen. Bei dieser Frage geht es um Java, die eine Sprache ist, die von der JVM nicht eingeschränkt wird, da die JVM-Spezifikation von geändert werden kann die gleichen Leute, die Java entwerfen.
- Und schließlich gibt es in der JVM nicht einmal eine Einschränkung in Bezug auf TRE, da die JVM über die Intra-Methode GOTO verfügt, die alles ist, was für TRE benötigt wird
1 Formatierung hinzugefügt, um gemachte Punkte hervorzuheben.
Antworten:
Wie von Brian Goetz (Java Language Architect bei Oracle) in diesem Video erklärt :
Alles, was die Anzahl der Frames auf dem Stack verändert, würde dies unterbrechen und einen Fehler verursachen. Er gibt zu, dass dies ein dummer Grund war, und so haben die JDK-Entwickler diesen Mechanismus inzwischen ersetzt.
Er erwähnt dann weiter, dass es keine Priorität ist, sondern diese Schwanzrekursion
Hinweis: Dies gilt für HotSpot und das OpenJDK. Andere VMs können davon abweichen.
quelle
Java hat keine Tail-Call-Optimierung aus dem gleichen Grund, aus dem es in den meisten imperativen Sprachen nicht verfügbar ist. Imperative Schleifen sind der bevorzugte Stil der Sprache, und der Programmierer kann die Endrekursion durch imperative Schleifen ersetzen. Die Komplexität lohnt sich nicht für ein Feature, dessen Verwendung aus Gründen des Stils nicht empfohlen wird.
Dieses Thema, bei dem Programmierer manchmal in einem FP-Stil in ansonsten zwingenden Sprachen schreiben möchten, ist erst in den letzten 10 Jahren oder so in Mode gekommen, nachdem Computer mit der Skalierung in Kernen anstelle von GHz begonnen hatten. Auch jetzt ist es nicht so beliebt. Wenn ich vorschlagen würde, eine imperative Schleife durch eine Endpunktrekursion bei der Arbeit zu ersetzen, würde die Hälfte der Codeüberprüfer lachen und die andere Hälfte würde verwirrt aussehen. Selbst in der funktionalen Programmierung vermeiden Sie im Allgemeinen die Schwanzrekursion, es sei denn, andere Konstrukte wie Funktionen höherer Ordnung passen nicht sauber zusammen.
quelle
for
Schleife verwenden" wäre (und auch für erstklassige Funktionen gegen Objekte). Ich würde nicht so weit gehen, es "imperative Bigotterie" zu nennen, aber ich glaube nicht, dass es 1995 sehr gefragt gewesen wäre, als die Hauptsorge wahrscheinlich Javas Geschwindigkeit und der Mangel an Generika war.Java bietet keine Optimierung für große Aufrufe, da die JVM keinen Bytecode für Endaufrufe enthält (zu einem statisch unbekannten Funktionszeiger, z. B. einer Methode in einer vtable).
Aus sozialen (und möglicherweise technischen) Gründen ist das Hinzufügen einer neuen Bytecode-Operation in der JVM (die sie mit früheren Versionen dieser JVM inkompatibel machen würde) für den Eigentümer der JVM-Spezifikation furchtbar schwierig.
Zu den technischen Gründen, warum in der JVM-Spezifikation kein neuer Bytecode hinzugefügt wurde, gehört die Tatsache, dass echte JVM-Implementierungen äußerst komplexe Softwareteile sind (z. B. aufgrund der vielen JIT-Optimierungen, die durchgeführt werden).
Bei Endaufrufen einer unbekannten Funktion muss der aktuelle Stack-Frame durch einen neuen ersetzt werden. Diese Operation sollte in der JVM stattfinden (es geht nicht nur darum, den Compiler für die Bytecode-Generierung zu ändern).
quelle
Sofern eine Sprache keine spezielle Syntax zum Ausführen eines Tail-Aufrufs (rekursiv oder auf andere Weise) hat und ein Compiler quietscht, wenn ein Tail-Aufruf angefordert wird, aber nicht generiert werden kann, führt die "optionale" Tail-Aufruf- oder Tail-Rekursionsoptimierung zu Situationen, in denen ein Teil ausgegeben wird Code erfordert möglicherweise weniger als 100 Byte Stapel auf einem Computer, aber mehr als 100.000.000 Byte Stapel auf einem anderen Computer. Solch ein Unterschied sollte eher als qualitativ als nur als quantitativ angesehen werden.
Es wird erwartet, dass Maschinen unterschiedliche Stapelgrößen haben, und daher ist es immer möglich, dass Code auf einer Maschine funktioniert, aber den Stapel auf einer anderen sprengt. Im Allgemeinen funktioniert Code, der auf einem Computer ausgeführt wird, auch wenn der Stapel künstlich eingeschränkt ist, wahrscheinlich auf allen Computern mit "normalen" Stapelgrößen. Wenn jedoch eine Methode, die 1.000.000 tief rekursiv ist, auf einer Maschine, aber nicht auf einer anderen, tail-call-optimiert ist, funktioniert die Ausführung auf der ersten Maschine wahrscheinlich auch dann, wenn ihr Stapel ungewöhnlich klein ist und auf der zweiten Maschine selbst dann fehlschlägt, wenn ihr Stapel ungewöhnlich groß ist .
quelle
Ich denke, die Tail-Call-Rekursion wird in Java hauptsächlich deshalb nicht verwendet, weil dadurch die Stack-Traces geändert und das Debuggen eines Programms erheblich erschwert würden. Ich denke, eines der Hauptziele von Java ist es, Programmierern ein einfaches Debuggen ihres Codes zu ermöglichen, und die Stapelverfolgung ist dafür besonders in einer stark objektorientierten Programmierumgebung von entscheidender Bedeutung. Da stattdessen eine Iteration verwendet werden könnte, muss das Sprachkomitee angenommen haben, dass es sich nicht lohnt, eine Schwanzrekursion hinzuzufügen.
quelle