Saya ingin tahu mengapa fungsi ini bekerja di java dan juga di kotlin dengan tailrec
tetapi tidak di kotlin tanpatailrec
?
Jawaban singkatnya adalah karena metode Kotlin Anda "lebih berat" daripada yang JAVA . Pada setiap panggilan itu memanggil metode lain yang "memprovokasi"StackOverflowError
. Jadi, lihat penjelasan lebih rinci di bawah ini.
Setara bytecode Java untuk reverseString()
Saya memeriksa kode byte untuk metode Anda di Kotlin dan JAVA sesuai:
Metode bytecode Kotlin di 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);
}
}
...
Metode bytecode JAVA di 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);
}
}
...
Jadi, ada 2 perbedaan utama:
Intrinsics.checkParameterIsNotNull(s, "s")
dipanggil untuk masing - masing helper()
dalam versi Kotlin .
- Indeks kiri dan kanan dalam metode JAVA bertambah, sementara di Kotlin indeks baru dibuat untuk setiap panggilan rekursif.
Jadi, mari kita uji seberapa Intrinsics.checkParameterIsNotNull(s, "s")
sendirian mempengaruhi perilaku.
Uji kedua implementasi
Saya telah membuat tes sederhana untuk kedua kasus:
@Test
public void testJavaImplementation() {
char[] chars = new char[20000];
new Example().reverseString(chars);
}
Dan
@Test
fun testKotlinImplementation() {
val chars = CharArray(20000)
Example().reverseString(chars)
}
Untuk JAVA tes berhasil tanpa masalah sedangkan untuk Kotlin gagal total karena a StackOverflowError
. Namun, setelah saya menambahkan Intrinsics.checkParameterIsNotNull(s, "s")
ke metode JAVA gagal juga:
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);
}
Kesimpulan
Metode Kotlin Anda memiliki kedalaman rekursi yang lebih kecil karena memanggil Intrinsics.checkParameterIsNotNull(s, "s")
pada setiap langkah dan karenanya lebih berat daripada rekan JAVA- nya. Jika Anda tidak ingin metode yang dibuat secara otomatis ini, maka Anda dapat menonaktifkan pemeriksaan nol selama kompilasi seperti yang dijawab di sini
Namun, karena Anda memahami manfaat apa yang tailrec
membawa (mengubah panggilan rekursif Anda menjadi iteratif), Anda harus menggunakannya.
tailrec
, atau menghindari rekursi; ukuran tumpukan yang tersedia bervariasi antara proses, antara JVM dan pengaturan, dan tergantung pada metode dan paramsnya. Tetapi jika Anda bertanya karena penasaran murni (alasan yang sangat bagus!), Maka saya tidak yakin. Anda mungkin perlu melihat bytecode.