Ich möchte wissen, warum diese Funktion in Java und auch in Kotlin mit, tailrecaber nicht in Kotlin ohne funktionierttailrec .
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
...
public final void reverseString(@NotNull char[] s) {
Intrinsics.checkParameterIsNotNull(s, "s");
this.helper(0, ArraysKt.getLastIndex(s), s);
}
public final void helper(int i, int j, @NotNull char[] s) {
Intrinsics.checkParameterIsNotNull(s, "s");
if (i < j) {
char t = s[j];
s[j] = s[i];
s[i] = t;
this.helper(i + 1, j - 1, s);
}
}
...
Bytecode der JAVA-Methode in JAVA
...
public void reverseString(char[] s) {
this.helper(s, 0, s.length - 1);
}
public void helper(char[] s, int left, int right) {
if (left < right) {
char temp = s[left];
s[left++] = s[right];
s[right--] = temp;
this.helper(left, right, s);
}
}
...
Es gibt also zwei Hauptunterschiede:
Intrinsics.checkParameterIsNotNull(s, "s")wird für jeden helper()in der aufgerufen Kotlin- Version .
- Linke und rechte Indizes in der JAVA- Methode werden inkrementiert, während in Kotlin für jeden rekursiven Aufruf neue Indizes erstellt werden.
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:
@Test
public void testJavaImplementation() {
char[] chars = new char[20000];
new Example().reverseString(chars);
}
Und
@Test
fun testKotlinImplementation() {
val chars = CharArray(20000)
Example().reverseString(chars)
}
Für JAVA war der Test ohne Probleme erfolgreich, während er für Kotlin aufgrund von a kläglich fehlschlug StackOverflowError. Nachdem ich Intrinsics.checkParameterIsNotNull(s, "s")die JAVA- Methode hinzugefügt habe , ist dies ebenfalls fehlgeschlagen:
public void helper(char[] s, int left, int right) {
Intrinsics.checkParameterIsNotNull(s, "s"); // add the same call here
if (left >= right) return;
char tmp = s[left];
s[left] = s[right];
s[right] = tmp;
helper(s, left + 1, right - 1);
}
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 beantwortet
Da Sie jedoch verstehen, welchen Nutzen dies tailrecbringt (wandelt Ihren rekursiven Aufruf in einen iterativen um), sollten Sie diesen verwenden.
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.