ฉันต้องการทราบว่าทำไมฟังก์ชั่นนี้ใช้งานได้ในjavaและในkotlinด้วยtailrec
แต่ไม่ใช่kotlinหากไม่มีtailrec
?
คำตอบสั้น ๆ เป็นเพราะคุณKotlinวิธีคือ "หนัก" กว่าJAVAหนึ่ง ในทุกสายที่เรียกว่าวิธีการอื่นที่ StackOverflowError
"กระตุ้นความ" ดังนั้นดูคำอธิบายโดยละเอียดเพิ่มเติมด้านล่าง
Java bytecode เทียบเท่า reverseString()
ฉันตรวจสอบรหัสไบต์สำหรับวิธีการของคุณในKotlinและJAVAตามลำดับ:
วิธี Kotlin bytecode ใน 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 bytecode ใน 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การทดสอบประสบความสำเร็จได้โดยไม่มีปัญหาในขณะที่สำหรับKotlinStackOverflowError
มันล้มเหลวอย่างน่าสังเวชเนื่องจากการ อย่างไรก็ตามหลังจากที่ผมเพิ่ม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วิธีการมีความลึก recursion ที่มีขนาดเล็กที่สุดเท่าที่มันจะเรียกIntrinsics.checkParameterIsNotNull(s, "s")
ในทุกขั้นตอนและทำให้หนักกว่าของJAVAคู่ หากคุณไม่ต้องการวิธีการที่สร้างขึ้นอัตโนมัตินี้คุณสามารถปิดการใช้งานการตรวจสอบโมฆะในระหว่างการรวบรวมตามคำตอบที่นี่
อย่างไรก็ตามเนื่องจากคุณเข้าใจถึงประโยชน์ที่tailrec
จะได้รับ(แปลงสายเรียกซ้ำของคุณเป็นสายซ้ำ) คุณควรใช้สายนั้น
tailrec
หรือหลีกเลี่ยงการเรียกซ้ำ ขนาดสแต็กที่มีให้จะแตกต่างกันระหว่างการวิ่งระหว่าง JVM และการตั้งค่าและขึ้นอยู่กับวิธีการและพารามิเตอร์ แต่ถ้าคุณถามถึงความอยากรู้อย่างแท้จริง (เป็นเหตุผลที่ดีอย่างสมบูรณ์แบบ!) ฉันก็ไม่แน่ใจ คุณอาจต้องดูที่ bytecode