Я хочу знати, чому ця функція працює в java, а також в kotlin з, tailrecале не в kotlin без tailrec?
Коротка відповідь полягає в тому, що ваш метод Котліна "важчий", ніж метод JAVA . На кожен дзвінок він називає інший метод, який "провокує" StackOverflowError. Отже, докладніше пояснення див. Нижче.
Еквіваленти байт-коду Java для reverseString()
Я перевірив байт-код на ваші методи у Котліні та JAVA відповідно:
Байт-код методу Котліна в 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);
}
}
...
Байт-код методу JAVA в 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);
}
}
...
Отже, є 2 основні відмінності:
Intrinsics.checkParameterIsNotNull(s, "s")викликається для кожного helper()у версії Котліна .
- Ліві та праві індекси методу JAVA збільшуються, тоді як у Котліні створюються нові індекси для кожного рекурсивного виклику.
Отже, давайте перевіримо, як Intrinsics.checkParameterIsNotNull(s, "s")поодинці впливає на поведінку.
Перевірте обидві реалізації
Я створив простий тест для обох випадків:
@Test
public void testJavaImplementation() {
char[] chars = new char[20000];
new Example().reverseString(chars);
}
І
@Test
fun testKotlinImplementation() {
val chars = CharArray(20000)
Example().reverseString(chars)
}
Для JAVA тест вдався без проблем, тоді як для Котліна він зазнав невдач через a StackOverflowError. Однак після того, як я додав Intrinsics.checkParameterIsNotNull(s, "s")до методу JAVA, він також не вдався:
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);
}
Висновок
Ваш метод Котліна має меншу глибину рекурсії, оскільки він викликає Intrinsics.checkParameterIsNotNull(s, "s")на кожному кроці і, таким чином, важчий, ніж його аналог JAVA . Якщо ви не хочете цього автоматично створеного методу, ви можете відключити нульові перевірки під час компіляції, як тут відповіли
Однак, оскільки ви розумієте, що tailrecприносить користь (перетворює ваш рекурсивний дзвінок в ітеративний), вам слід скористатися цією.
tailrecабо уникнення рекурсії; доступний розмір стека залежить від прогонів, між JVM і налаштуваннями, і залежно від методу та його параметрів. Але якщо ви просите з чистої цікавості (цілком поважна причина!), То я не впевнений. Вам, мабуть, доведеться подивитися на байт-код.