Eu tenho dois códigos quase idênticos 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 passa no teste com uma entrada enorme, mas o código kotlin causa uma a StackOverFlowError
menos que eu tenha adicionado uma tailrec
palavra-chave antes da helper
função no kotlin.
Eu quero saber por que essa função funciona em java e também no kolin com tailrec
mas sem no kotlin sem tailrec
?
PS:
eu sei o que tailrec
fazer
tailrec
ou evitar a recursão; o tamanho da pilha disponível varia entre execuções, entre JVMs e configurações e dependendo do método e de seus parâmetros. Mas se você está perguntando por pura curiosidade (uma razão perfeitamente boa!), Não tenho certeza. Você provavelmente precisaria olhar o código de bytes.Respostas:
A resposta curta é porque seu método Kotlin é "mais pesado" que o método JAVA . A cada chamada, chama outro método que "provoca"
StackOverflowError
. Então, veja uma explicação mais detalhada abaixo.Equivalentes de bytecode Java para
reverseString()
Eu verifiquei o código de bytes para seus métodos no Kotlin e JAVA correspondentemente:
Método de Kotlin bytecode em JAVA
Bytecode do método JAVA em JAVA
Então, existem 2 diferenças principais:
Intrinsics.checkParameterIsNotNull(s, "s")
é chamado para cada umhelper()
na versão Kotlin .Então, vamos testar como
Intrinsics.checkParameterIsNotNull(s, "s")
sozinho afeta o comportamento.Teste as duas implementações
Eu criei um teste simples para os dois casos:
E
Para JAVA, o teste foi bem-sucedido sem problemas, enquanto para Kotlin falhou miseravelmente devido a um
StackOverflowError
. No entanto, depois que adicioneiIntrinsics.checkParameterIsNotNull(s, "s")
ao método JAVA , ele também falhou:Conclusão
Seu método Kotlin possui uma profundidade de recursão menor, pois invoca
Intrinsics.checkParameterIsNotNull(s, "s")
a cada passo e, portanto, é mais pesado que o equivalente em JAVA . Se você não quiser esse método gerado automaticamente, poderá desativar as verificações nulas durante a compilação, conforme respondidas aquiNo entanto, como você entende o que o benefício
tailrec
traz (converte sua chamada recursiva em iterativa), você deve usá-la.fonte
Intrinsics.checkParameterIsNotNull(...)
. Obviamente, cada um desses quadros de pilha exige uma certa quantidade de memória (para aLocalVariableTable
pilha e operando e assim por diante) ..O Kotlin tem um pouco mais de fome de pilha (objetos de parâmetro int io parâmetros de int). Além da solução tailrec que se encaixa aqui, você pode eliminar a variável local
temp
xor-ing:Não tenho certeza se isso funciona para remover uma variável local.
A eliminação de j também pode fazer:
fonte