Tôi muốn biết lý do tại sao chức năng này hoạt động trong java và cũng trong Kotlin với tailrec
nhưng không phải trong Kotlin không tailrec
?
Câu trả lời ngắn gọn là vì phương pháp Kotlin của bạn "nặng" hơn phương pháp JAVA . Tại mỗi cuộc gọi, nó gọi một phương thức khác là "khiêu khích" StackOverflowError
. Vì vậy, xem một lời giải thích chi tiết hơn dưới đây.
Tương đương mã byte Java cho reverseString()
Tôi đã kiểm tra mã byte cho các phương thức của bạn trong Kotlin và JAVA tương ứng:
Mã phương thức Kotlin trong 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);
}
}
...
Phương thức JAVA mã byte trong 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);
}
}
...
Vì vậy, có 2 điểm khác biệt chính:
Intrinsics.checkParameterIsNotNull(s, "s")
được gọi cho mỗi helper()
trong phiên bản Kotlin .
- Các chỉ mục bên trái và bên phải trong phương thức JAVA được tăng lên, trong khi trong Kotlin, các chỉ mục mới được tạo cho mỗi cuộc gọi đệ quy.
Vì vậy, hãy kiểm tra xem Intrinsics.checkParameterIsNotNull(s, "s")
một mình ảnh hưởng đến hành vi như thế nào .
Kiểm tra cả hai triển khai
Tôi đã tạo một thử nghiệm đơn giản cho cả hai trường hợp:
@Test
public void testJavaImplementation() {
char[] chars = new char[20000];
new Example().reverseString(chars);
}
Và
@Test
fun testKotlinImplementation() {
val chars = CharArray(20000)
Example().reverseString(chars)
}
Đối với JAVA , bài kiểm tra đã thành công mà không gặp vấn đề gì trong khi đối với Kotlin, nó đã thất bại thảm hại do a StackOverflowError
. Tuy nhiên, sau khi tôi thêm Intrinsics.checkParameterIsNotNull(s, "s")
vào JAVA phương pháp đó thất bại cũng như:
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);
}
Phần kết luận
Phương pháp Kotlin của bạn có độ sâu đệ quy nhỏ hơn khi nó gọi Intrinsics.checkParameterIsNotNull(s, "s")
ở mọi bước và do đó nặng hơn so với đối tác JAVA của nó . Nếu bạn không muốn phương thức được tạo tự động này thì bạn có thể tắt kiểm tra null trong quá trình biên dịch như đã trả lời ở đây
Tuy nhiên, vì bạn hiểu lợi ích tailrec
mang lại (chuyển cuộc gọi đệ quy của bạn thành cuộc gọi lặp lại), bạn nên sử dụng cuộc gọi đó.
tailrec
hoặc tránh đệ quy; kích thước ngăn xếp có sẵn khác nhau giữa các lần chạy, giữa các JVM và các thiết lập, và tùy thuộc vào phương thức và các tham số của nó. Nhưng nếu bạn hỏi về sự tò mò thuần túy (một lý do hoàn toàn chính đáng!), Thì tôi không chắc chắn. Bạn có thể cần phải nhìn vào mã byte.