chamada de método recursivo causa StackOverflowError em Kotlin mas não em java

hamid_c:

Eu tenho dois código quase idêntico em java e 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)
}

O código java passar no teste com uma enorme entrada, mas o código Kotlin causa a StackOverFlowErrormenos que eu adicionei tailrecpalavra-chave antes da helperfunção em Kotlin.

Eu quero saber por que essa função funciona em java e também em Kolin com tailrecmas não em Kotlin sem tailrec?

PS: eu sei o que tailrecfazer

Anatolii:

Eu quero saber por que essa função funciona em java e também em Kotlin com tailrecmas não em Kotlin sem tailrec?

A resposta curta é porque o seu Kotlin método é "mais pesado" do que o JAVA um. Em cada chamada que chama outro método que "provoca" StackOverflowError. Então, ver uma explicação mais detalhada abaixo.

equivalentes bytecode Java para reverseString()

Eu verifiquei o código de byte para os seus métodos em Kotlin e JAVA correspondentemente:

método Kotlin código de bytes em 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 código de bytes em 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);
    }
}
...

Então, lá está 2 diferenças principais:

  1. Intrinsics.checkParameterIsNotNull(s, "s")é invocado para cada helper()no Kotlin versão.
  2. Índices de esquerda e direita em JAVA método se incrementado, enquanto em Kotlin novos índices são criados para cada chamada recursiva.

Assim, o teste de deixar como Intrinsics.checkParameterIsNotNull(s, "s")só afeta o comportamento.

Testar ambas as implementações

Eu criei um teste simples para ambos os casos:

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

E

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

Para JAVA o teste teve sucesso sem problemas enquanto para Kotlin falhou miseravelmente, devido a uma StackOverflowError. No entanto, depois que eu adicionei Intrinsics.checkParameterIsNotNull(s, "s")ao JAVA método que falhou, bem como:

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

Conclusão

Seu Kotlin método tem uma profundidade de recursão menor, uma vez que invoca Intrinsics.checkParameterIsNotNull(s, "s")a cada passo e, portanto, é mais pesado do que o seu JAVA homólogo. Se você não quiser este método gerado automaticamente, então você pode desabilitar a verificação de nulos durante a compilação como respondida aqui

No entanto, desde que você entender o que benefício tailrectraz (converte a sua chamada recursiva em um um iterativo), você deve usar esse.

Acho que você gosta

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