J'ai deux codes presque identiques en java et 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)
}
Le code java réussit le test avec une énorme entrée mais le code kotlin provoque un mot-clé StackOverFlowError
sauf si j'ai ajouté tailrec
avant la helper
fonction dans kotlin.
Je veux savoir pourquoi cette fonction fonctionne en java et aussi en kolin avec tailrec
mais pas en kotlin sans tailrec
?
PS:
je sais quoi tailrec
faire
tailrec
ou d'éviter la récursivité; la taille de pile disponible varie entre les exécutions, entre les machines virtuelles Java et les configurations, et selon la méthode et ses paramètres. Mais si vous demandez par pure curiosité (une raison parfaitement bonne!), Alors je ne suis pas sûr. Vous auriez probablement besoin de regarder le bytecode.Réponses:
La réponse courte est que votre méthode Kotlin est "plus lourde" que celle de JAVA . A chaque appel, il appelle une autre méthode qui "provoque"
StackOverflowError
. Donc, voyez une explication plus détaillée ci-dessous.Équivalents de bytecode Java pour
reverseString()
J'ai vérifié le code d'octet pour vos méthodes dans Kotlin et JAVA en conséquence:
Bytecode de la méthode Kotlin dans JAVA
Bytecode de la méthode JAVA dans JAVA
Donc, il y a 2 différences principales:
Intrinsics.checkParameterIsNotNull(s, "s")
est invoqué pour chacunhelper()
dans la version Kotlin .Alors, testons comment
Intrinsics.checkParameterIsNotNull(s, "s")
seul affecte le comportement.Testez les deux implémentations
J'ai créé un test simple pour les deux cas:
Et
Pour JAVA, le test a réussi sans problème tandis que pour Kotlin, il a lamentablement échoué en raison d'un
StackOverflowError
. Cependant, après avoir ajoutéIntrinsics.checkParameterIsNotNull(s, "s")
à la méthode JAVA , elle a également échoué:Conclusion
Votre méthode Kotlin a une profondeur de récursivité plus petite car elle invoque
Intrinsics.checkParameterIsNotNull(s, "s")
à chaque étape et est donc plus lourde que son homologue JAVA . Si vous ne voulez pas de cette méthode générée automatiquement, vous pouvez désactiver les vérifications nulles pendant la compilation comme indiqué iciCependant, puisque vous comprenez quel avantage
tailrec
apporte (convertit votre appel récursif en appel itératif), vous devez utiliser celui-ci.la source
Intrinsics.checkParameterIsNotNull(...)
. De toute évidence, chacune de ces trames de pile nécessite une certaine quantité de mémoire (pour laLocalVariableTable
pile d'opérandes et ainsi de suite).Kotlin est juste un peu plus gourmand en pile (paramètres d'objet int io paramètres int). Outre la solution tailrec qui convient ici, vous pouvez éliminer la variable locale
temp
par xor-ing:Je ne sais pas vraiment si cela fonctionne pour supprimer une variable locale.
L'élimination de j pourrait également faire:
la source