Ich habe zwei fast identische Codes in Java und Kotlin
Java:
public void reverseString(char[] s) {
helper(s, 0, s.length - 1);
}
public void helper(char[] s, int left, int right) {
if (left >= right) return;
char tmp = s[left];
s[left++] = s[right];
s[right--] = tmp;
helper(s, left, right);
}
Kotlin:
fun reverseString(s: CharArray): Unit {
helper(0, s.lastIndex, s)
}
fun helper(i: Int, j: Int, s: CharArray) {
if (i >= j) {
return
}
val t = s[j]
s[j] = s[i]
s[i] = t
helper(i + 1, j - 1, s)
}
Der Java-Code besteht den Test mit einer großen Eingabe, aber der Kotlin-Code verursacht ein, es StackOverFlowError
sei denn, ich habe tailrec
vor der helper
Funktion in Kotlin ein Schlüsselwort hinzugefügt.
Ich möchte wissen, warum diese Funktion in Java und auch in Kolin mit, tailrec
aber nicht in Kotlin ohne funktioniert tailrec
.
PS:
Ich weiß was zu tailrec
tun ist
tailrec
, eine Rekursion zu verwenden oder zu vermeiden. Die verfügbare Stapelgröße variiert zwischen Läufen, zwischen JVMs und Setups und hängt von der Methode und ihren Parametern ab. Aber wenn Sie aus purer Neugier fragen (ein guter Grund!), Dann bin ich mir nicht sicher. Sie müssten sich wahrscheinlich den Bytecode ansehen.Antworten:
Die kurze Antwort lautet, dass Ihre Kotlin- Methode "schwerer" ist als die JAVA- Methode . Bei jedem Aufruf wird eine andere Methode aufgerufen, die "provoziert"
StackOverflowError
. Eine ausführlichere Erklärung finden Sie weiter unten.Java-Bytecode-Äquivalente für
reverseString()
Ich habe den Bytecode für Ihre Methoden in Kotlin und JAVA entsprechend überprüft :
Bytecode der Kotlin-Methode in JAVA
Bytecode der JAVA-Methode in JAVA
Es gibt also zwei Hauptunterschiede:
Intrinsics.checkParameterIsNotNull(s, "s")
wird für jedenhelper()
in der aufgerufen Kotlin- Version .Testen wir also, wie
Intrinsics.checkParameterIsNotNull(s, "s")
allein das Verhalten auswirkt.Testen Sie beide Implementierungen
Ich habe für beide Fälle einen einfachen Test erstellt:
Und
Für JAVA war der Test ohne Probleme erfolgreich, während er für Kotlin aufgrund von a kläglich fehlschlug
StackOverflowError
. Nachdem ichIntrinsics.checkParameterIsNotNull(s, "s")
die JAVA- Methode hinzugefügt habe , ist dies ebenfalls fehlgeschlagen:Fazit
Ihre Kotlin- Methode hat eine geringere Rekursionstiefe, da sie
Intrinsics.checkParameterIsNotNull(s, "s")
bei jedem Schritt aufgerufen wird und daher schwerer als ihre JAVA ist Gegenstück. Wenn Sie diese automatisch generierte Methode nicht möchten, können Sie die Nullprüfungen während der Kompilierung deaktivieren, wie hier beantwortetDa Sie jedoch verstehen, welchen Nutzen dies
tailrec
bringt (wandelt Ihren rekursiven Aufruf in einen iterativen um), sollten Sie diesen verwenden.quelle
Intrinsics.checkParameterIsNotNull(...)
. Offensichtlich benötigt jeder solche Stapelrahmen eine bestimmte Menge an Speicher (für denLocalVariableTable
und Operandenstapel usw.).Kotlin ist nur ein bisschen stapelhungriger (Int object params io int params). Neben der hier passenden tailrec-Lösung können Sie die lokale Variable
temp
durch xor-ing entfernen :Nicht ganz sicher, ob dies funktioniert, um eine lokale Variable zu entfernen.
Auch das Eliminieren von j könnte Folgendes bewirken:
quelle