Java >> Java tutorial >  >> Tag >> Stack

Rekursiv metodekald forårsager StackOverFlowError i kotlin, men ikke i java

Jeg vil gerne vide, hvorfor denne funktion virker i java og også i kotlin med tailrec men ikke i kotlin uden tailrec ?

Det korte svar er, fordi din Kotlin metoden er "tyngre" end JAVA en. Ved hvert opkald kalder den en anden metode, der "provokerer" StackOverflowError . Så se en mere detaljeret forklaring nedenfor.

Java-bytekodeækvivalenter for reverseString()

Jeg tjekkede bytekoden for dine metoder i Kotlin og JAVA tilsvarende:

Kotlin-metodens bytekode i 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);
    }
}
...

JAVA-metodens bytekode i 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);
    }
}
...

Så der er 2 hovedforskelle:

  1. Intrinsics.checkParameterIsNotNull(s, "s") kaldes for hver helper() i Kotlin version.
  2. Venstre og højre indeks i JAVA metode bliver inkrementeret, mens den er i Kotlin nye indekser oprettes for hvert rekursivt kald.

Så lad os teste hvordan Intrinsics.checkParameterIsNotNull(s, "s") alene påvirker adfærden.

Test begge implementeringer

Jeg har lavet en simpel test for begge tilfælde:

@Test
public void testJavaImplementation() {
    char[] chars = new char[20000];
    new Example().reverseString(chars);
}

Og

@Test
fun testKotlinImplementation() {
    val chars = CharArray(20000)
    Example().reverseString(chars)
}

Til JAVA testen lykkedes uden problemer under Kotlin det mislykkedes dybt på grund af en StackOverflowError . Men efter at jeg tilføjede Intrinsics.checkParameterIsNotNull(s, "s") til JAVA metode mislykkedes det også:

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);
}

Konklusion

Din Kotlin metoden har en mindre rekursionsdybde, da den kalder Intrinsics.checkParameterIsNotNull(s, "s") ved hvert trin og er dermed tungere end dens JAVA modpart. Hvis du ikke ønsker denne automatisk genererede metode, kan du deaktivere nul-tjek under kompilering som besvaret her

Men da du forstår hvilken fordel tailrec bringer (konverterer dit rekursive kald til et iterativt), bør du bruge det.


Java tag