Chcę wiedzieć, dlaczego ta funkcja działa w java, a także w kotlin z kotlin bez, tailrec
ale nie w kotlinie bez tailrec
?
Krótka odpowiedź brzmi, ponieważ twoja metoda Kotlina jest „cięższa” niż metoda JAVA . Przy każdym wywołaniu wywołuje inną metodę, która „prowokuje” StackOverflowError
. Zobacz więc bardziej szczegółowe wyjaśnienie poniżej.
Ekwiwalenty kodu bajtowego Java dla reverseString()
Sprawdziłem kod bajtu dla twoich metod odpowiednio w Kotlin i JAVA :
Kod bajtowy metody Kotlin w 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);
}
}
...
Kod bajtowy metody JAVA w 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);
}
}
...
Istnieją więc dwie główne różnice:
Intrinsics.checkParameterIsNotNull(s, "s")
jest wywoływany dla każdego helper()
w wersji Kotlin .
- Wskaźniki lewy i prawy w metodzie JAVA są zwiększane, natomiast w Kotlinie tworzone są nowe indeksy dla każdego wywołania rekurencyjnego.
Przetestujmy, jak Intrinsics.checkParameterIsNotNull(s, "s")
sam wpływa na zachowanie.
Przetestuj obie implementacje
Stworzyłem prosty test dla obu przypadków:
@Test
public void testJavaImplementation() {
char[] chars = new char[20000];
new Example().reverseString(chars);
}
I
@Test
fun testKotlinImplementation() {
val chars = CharArray(20000)
Example().reverseString(chars)
}
W przypadku JAVA test powiódł się bez problemów, podczas gdy w przypadku Kotlin nie powiódł się z powodu błędu StackOverflowError
. Jednakże, po dodaniu Intrinsics.checkParameterIsNotNull(s, "s")
do JAVA sposób nie udało się także:
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);
}
Wniosek
Twoja metoda Kotlin ma mniejszą głębokość rekurencji, ponieważ wywołuje ją Intrinsics.checkParameterIsNotNull(s, "s")
na każdym kroku, a zatem jest cięższa niż jej odpowiednik JAVA . Jeśli nie chcesz tej automatycznie generowanej metody, możesz wyłączyć sprawdzanie wartości NULL podczas kompilacji, zgodnie z odpowiedzią tutaj
Ponieważ jednak rozumiesz, co tailrec
przynosi korzyść (zamienia rekursywne wywołanie w iteracyjne), powinieneś go użyć.
tailrec
lub uniknięcie rekurencji; dostępny rozmiar stosu różni się między przebiegami, między maszynami JVM i konfiguracjami oraz w zależności od metody i jej parametrów. Ale jeśli pytasz z czystej ciekawości (doskonały powód!), To nie jestem pewien. Prawdopodobnie będziesz musiał spojrzeć na kod bajtowy.