Recursiva llamada al método causa StackOverflowError en Kotlin pero no en java

hamid_c:

Tengo dos código casi idéntica en Java y 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)
}

El código Java pasa la prueba con una gran entrada, pero el código Kotlin causa una StackOverFlowErrormenos que añadí tailrecpalabra clave antes de la helperfunción en Kotlin.

Quiero saber por qué esta opción funciona en Java y también en Kolin con tailrecpero no en Kotlin sin tailrec?

PS: Yo sé lo que tailrechacer

anatolii:

Quiero saber por qué esta función trabaja en java y también en Kotlin con tailrecpero no en Kotlin sin tailrec?

La respuesta corta es porque su Kotlin método es más "pesado" que el JAVA uno. En cada llamada del que llama a otro método que "provoca" StackOverflowError. Por lo tanto, ver una explicación más detallada a continuación.

equivalentes de código de bytes de Java para reverseString()

He comprobado el código de bytes para sus métodos en Kotlin y JAVA en consecuencia:

método Kotlin bytecode en 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);
    }
}
...

método Java bytecode en 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);
    }
}
...

Por lo tanto, hay 2 diferencias principales:

  1. Intrinsics.checkParameterIsNotNull(s, "s")se invoca para cada uno helper()en el Kotlin versión.
  2. Izquierdo y derecho índices en JAVA método GET incrementan, mientras que en Kotlin se crean nuevos índices para cada llamada recursiva.

Por lo tanto, la prueba de Let cómo Intrinsics.checkParameterIsNotNull(s, "s")solo afecta al comportamiento.

Probar ambas implementaciones

He creado una prueba simple para ambos casos:

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

Y

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

Para JAVA la prueba tuvo éxito y sin problemas mientras que para Kotlin fracasó miserablemente debido a una StackOverflowError. Sin embargo, después de añadir Intrinsics.checkParameterIsNotNull(s, "s")a la JAVA método fracasó así:

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

Conclusión

Su Kotlin método tiene un nivel de recursividad más pequeño, ya que invoca Intrinsics.checkParameterIsNotNull(s, "s")a cada paso y por lo tanto es más pesado que su JAVA contraparte. Si no desea que este método de auto-generado, entonces puede desactivar cheques nulos durante la compilación como respondida aquí

Sin embargo, puesto que entiende qué beneficio tailrectrae (convierte la llamada recursiva en un proceso iterativo), debe usar esa.

Supongo que te gusta

Origin http://10.200.1.11:23101/article/api/json?id=443308&siteId=1
Recomendado
Clasificación