[英]Recursive method call cause StackOverFlowError in kotlin but not in java
我在 java 和 kotlin 中有兩個幾乎相同的代碼
爪哇:
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);
}
科特林:
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)
}
java 代碼通過了大量輸入的測試,但 kotlin 代碼會導致StackOverFlowError
除非我在 kotlin 中的helper
函數之前添加了tailrec
關鍵字。
我想知道為什么這個函數在 java 和帶有tailrec
中有效,但在沒有tailrec
?
PS:我知道tailrec
做什么
我想知道為什么這個功能在Java中工作,並在科特林與
tailrec
但不是在科特林沒有tailrec
?
簡短的回答是因為您的Kotlin方法比JAVA方法“更重”。 在每次調用時,它都會調用另一個“引發” StackOverflowError
。 因此,請參閱下面更詳細的說明。
reverseString()
Java 字節碼等價物
我相應地在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);
}
}
...
因此,有兩個主要區別:
helper()
調用Intrinsics.checkParameterIsNotNull(s, "s")
。 因此,讓我們測試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對應方法更重。 如果您不想要這種自動生成的方法,那么您可以在編譯期間禁用空檢查,如回答here
但是,由於您了解tailrec
帶來的好處(將您的遞歸調用轉換為迭代調用),您應該使用它。
Kotlin 只需要一點點堆棧飢餓(Int object params io int params)。 除了適合此處的 tailrec 解決方案之外,您還可以通過異或運算消除局部變量temp
:
fun helper(i: Int, j: Int, s: CharArray) {
if (i >= j) {
return
} // i: a j: b
s[j] ^= s[i] // j: a^b
s[i] ^= s[j] // i: a^a^b == b
s[j] ^= s[i] // j: a^b^b == a
helper(i + 1, j - 1, s)
}
不完全確定這是否適用於刪除局部變量。
同樣消除 j 可能會:
fun reverseString(s: CharArray): Unit {
helper(0, s)
}
fun helper(i: Int, s: CharArray) {
if (i >= s.lastIndex - i) {
return
}
val t = s[s.lastIndex - i]
s[s.lastIndex - i] = s[i]
s[i] = t
helper(i + 1, s)
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.