Je veux savoir pourquoi cette fonction fonctionne en java et aussi en kotlin avec tailrec
mais pas en kotlin sans tailrec
?
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
...
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);
}
}
...
Bytecode de la méthode JAVA dans 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);
}
}
...
Donc, il y a 2 différences principales:
Intrinsics.checkParameterIsNotNull(s, "s")
est invoqué pour chacun helper()
dans la version Kotlin .
- Les indices gauche et droit de la méthode JAVA sont incrémentés, tandis que dans Kotlin, de nouveaux indices sont créés pour chaque appel récursif.
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:
@Test
public void testJavaImplementation() {
char[] chars = new char[20000];
new Example().reverseString(chars);
}
Et
@Test
fun testKotlinImplementation() {
val chars = CharArray(20000)
Example().reverseString(chars)
}
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é:
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);
}
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é ici
Cependant, puisque vous comprenez quel avantage tailrec
apporte (convertit votre appel récursif en appel itératif), vous devez utiliser celui-ci.
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.