我想知道为什么这个函数在java中有效,在kotlin中有tailrec
,但在kotlin中没有tailrec
?
简短的答案是因为您的Kotlin方法比JAVA方法"重".在每次调用时,它都会调用另一个"激发"StackOverflowError
的方法.因此,请参阅下面更详细的说明.
Java bytecode equivalents for reverseString()
我在Kotlin和JAVA中相应地判断了你的方法的字节码:
Kotlin method bytecode in 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 method bytecode in 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);
}
}
...
So, there're 2 main differences:
Intrinsics.checkParameterIsNotNull(s, "s")
is invoked for each helper()
in the Kotlin version.
- JAVA方法中的左索引和右索引递增,而在Kotlin方法中, for each 递归调用创建新索引.
So, let's test how Intrinsics.checkParameterIsNotNull(s, "s")
alone affects the behavior.
Test both implementations
我为这两种情况创建了一个简单的测试:
@Test
public void testJavaImplementation() {
char[] chars = new char[20000];
new Example().reverseString(chars);
}
和
@Test
fun testKotlinImplementation() {
val chars = CharArray(20000)
Example().reverseString(chars)
}
For JAVA the test succeeded without problems while for Kotlin it failed miserably due to a StackOverflowError
. However, after I added Intrinsics.checkParameterIsNotNull(s, "s")
to the JAVA method it failed as well:
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
Kotlin方法的递归深度较小,因为它在每一步调用Intrinsics.checkParameterIsNotNull(s, "s")
,因此比JAVA方法重.如果您不想使用这个自动生成的方法,那么可以在编译过程中禁用null判断,如下所示
然而,既然您了解tailrec
带来的好处(将递归调用转换为迭代调用),您就应该使用它.