我想知道为什么这个功能在工作的Java以及在科特林有tailrec
,但不是在科特林没有tailrec
?
简短的答案是因为您的Kotlin方法比JAVA方法“重” 。在每次调用时,它都会调用另一个“激发”的方法StackOverflowError
。因此,请参见下面的详细说明。
的Java字节码等效项 reverseString()
我相应地在Kotlin和JAVA中检查了字节代码以了解您的方法:
JAVA中的Kotlin方法字节码
...
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()
在Kotlin版本中为每个调用。
- JAVA方法中的左右索引递增,而在Kotlin中,为每个递归调用创建新索引。
因此,让我们测试一下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来说,测试成功了,没有出现任何问题;而对于Kotlin,由于测试失败,它惨败了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);
}
结论
您的Kotlin方法Intrinsics.checkParameterIsNotNull(s, "s")
在每个步骤都调用时具有较小的递归深度,因此比其JAVA方法要重。如果您不希望使用此自动生成的方法,则可以在编译过程中禁用null检查,如此处所示
但是,由于您了解了带来的好处tailrec
(将递归调用转换为迭代调用),因此应该使用该优点。
tailrec
或避免递归;可用的堆栈大小在运行之间,JVM和设置之间以及根据方法及其参数而有所不同。但是,如果您出于纯粹的好奇心而问(一个很好的理由!),那么我不确定。您可能需要查看字节码。