Kode Kotlin dapat memakai kelas, metode, dan field Java tanpa lapisan pembungkus apa pun, karena keduanya dikompilasi menjadi bytecode JVM yang sama. Topik ini menunjukkan cara memanggil kelas Java dari Kotlin, perilaku platform type pada nilai yang berasal dari API Java, peran anotasi nullability pada kode Java, serta risiko NullPointerException (NPE) yang menyertainya. Seluruh contoh menggunakan satu kelas Java sederhana dan tiga program Kotlin yang menjalankannya dari terminal.
Kelas Java yang dipakai dari Kotlin#
Kelas Java biasa sudah dapat dipanggil dari Kotlin apa adanya, tanpa perubahan kode sumber Java. Kelas Arsip berikut mewakili arsip cadangan berkas dengan dua field privat beserta pasangan getter dan setter untuk masing-masing.
// tidak-dijalankan
// Kelas Java sederhana untuk arsip cadangan berkas
public class Arsip {
private String nama;
private int jumlahBerkas;
public String getNama() {
return nama;
}
public void setNama(String nilai) {
this.nama = nilai;
}
public int getJumlahBerkas() {
return jumlahBerkas;
}
public void setJumlahBerkas(int nilai) {
this.jumlahBerkas = nilai;
}
}Program Kotlin berikut membuat instans Arsip, mengisi nilainya, lalu membacanya kembali. Pemanggilan setNama ditulis sebagai metode biasa, sedangkan jumlahBerkas ditulis seperti properti Kotlin.
// Memakai kelas Java Arsip dari kode Kotlin
fun main() {
val arsip = Arsip()
arsip.setNama("laporan-2024")
arsip.jumlahBerkas = 24
println(arsip.nama)
println(arsip.jumlahBerkas)
}Kompilasi dilakukan berurutan dari terminal. Kelas Java dikompilasi terlebih dahulu dengan javac, kemudian program Kotlin dikompilasi dengan kelas tersebut pada classpath, dan hasilnya dijalankan dengan java.
export JAVA_HOME=/home/kal/my-project/tools/jdk-merged
export PATH=$JAVA_HOME/bin:/home/kal/my-project/tools/kotlinc/bin:$PATH
export STDLIB=/home/kal/my-project/tools/kotlinc/lib/kotlin-stdlib.jar
# mengompilasi kelas Java terlebih dahulu
javac Arsip.java
# mengompilasi program Kotlin dengan kelas Java pada classpath
kotlinc main.kt -cp . -d out.jar
# menjalankan program dengan library standar Kotlin pada classpath
java -cp "out.jar:$STDLIB:." MainKtlaporan-2024
24Baris arsip.jumlahBerkas = 24 memanggil setJumlahBerkas(24) di belakang layar, sedangkan arsip.jumlahBerkas memanggil getJumlahBerkas(). Compiler Kotlin mengenali pola getter dan setter pada kelas Java lalu menghadirkannya sebagai properti sintetis. Tubuh kelas Arsip tidak perlu diubah sedikit pun agar pola ini berfungsi, sehingga kode Java lama dapat dipakai sebagaimana adanya.
Platform type dan risiko NPE#
Tipe nilai yang berasal dari API Java diperlakukan secara khusus oleh compiler Kotlin. Tipe semacam ini disebut platform type, yaitu tipe yang nullability-nya belum diketahui karena kode Java tidak menyatakannya. Platform type ditampilkan di IDE dengan tanda seru, misalnya String!, dan satu nilai platform type yang sama boleh diberikan ke variabel String maupun String?. Kemudahan ini ada bayarannya: compiler tidak menyisipkan pemeriksaan null pada nilai platform type, sehingga pemeriksaannya tergeser ke runtime.
Program berikut memanggil getNama() pada objek yang belum pernah diisi, sehingga metode Java tersebut mengembalikan null.
// Memanggil getter Java yang mengembalikan null
fun main() {
val arsip = Arsip()
println(arsip.nama.length)
}Kode di atas lolos kompilasi tanpa satu pun peringatan, karena hasil arsip.nama adalah platform type yang dianggap bebas dari null. Kesalahan baru terlihat saat program berjalan.
export JAVA_HOME=/home/kal/my-project/tools/jdk-merged
export PATH=$JAVA_HOME/bin:/home/kal/my-project/tools/kotlinc/bin:$PATH
export STDLIB=/home/kal/my-project/tools/kotlinc/lib/kotlin-stdlib.jar
javac Arsip.java
kotlinc npe.kt -cp . -d npe.jar
java -cp "npe.jar:$STDLIB:." NpeKtException in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because the return value of "Arsip.getNama()" is null
at NpeKt.main(npe.kt:4)
at NpeKt.main(npe.kt)Pesan pada keluaran menyebut Arsip.getNama() secara eksplisit karena pemeriksaan null dilakukan JVM saat metode length() dipanggil pada nilai yang ternyata null. Tidak ada baris kode Kotlin yang salah secara sintaks, dan tidak ada peringatan kompilasi yang bisa ditelusuri. Sumber masalahnya murni perbedaan kontrak: Kotlin menganggap nilai tersebut ada, sedangkan Java mengembalikan null.
Anotasi nullability pada kode Java#
Pengembang Java menyatakan kontrak null melalui anotasi. Pasangan @Nullable dan @NotNull dari paket org.jetbrains.annotations adalah keluarga yang dibaca compiler Kotlin secara langsung. Parameter atau nilai kembalian yang diberi @Nullable diperlakukan sebagai tipe nullable, sedangkan yang diberi @NotNull diperlakukan sebagai tipe non-null.
// tidak-dijalankan
import org.jetbrains.annotations.NotNull;
public class Arsip {
private String nama;
@NotNull
public String getNama() {
return nama;
}
}Apabila getter pada contoh diberi @Nullable, setiap penggunaan hasilnya di Kotlin tanpa penanganan null langsung ditolak saat kompilasi, sehingga kesalahan ditemukan lebih awal. Apabila diberi @NotNull, kode Kotlin boleh memakai nilainya tanpa pemeriksaan null karena kontraknya sudah dinyatakan. Kedua anotasi tersebut tidak menjadi bagian dari JDK, melainkan berada pada library terpisah yang disertakan sebagai dependensi proyek. Banyak pustaka Java menyertakan anotasi semacam ini pada API publiknya, dan compiler Kotlin juga mengenali keluarga anotasi lain seperti JSR-305.
Menahan ketidakpastian dengan tipe Kotlin#
Bila kode Java tidak diberi anotasi apa pun, ketidakpastian platform type sebaiknya diakhiri sedini mungkin di sisi Kotlin. Caranya adalah dengan memberi tipe eksplisit pada variabel penampungnya, yaitu String? bila nilai boleh null, atau String bila null tidak diterima.
// Hasil Java dinyatakan nullable secara eksplisit
fun main() {
val arsip = Arsip()
arsip.nama = "catatan-umum"
val nama: String? = arsip.nama
println(nama?.uppercase())
println(nama?.length ?: 0)
}export JAVA_HOME=/home/kal/my-project/tools/jdk-merged
export PATH=$JAVA_HOME/bin:/home/kal/my-project/tools/kotlinc/bin:$PATH
export STDLIB=/home/kal/my-project/tools/kotlinc/lib/kotlin-stdlib.jar
javac Arsip.java
kotlinc batas.kt -cp . -d batas.jar
java -cp "batas.jar:$STDLIB:." BatasKtCATATAN-UMUM
12Variabel nama dinyatakan sebagai String?, sehingga setiap penggunaannya wajib melewati safe call ?. atau operator elvis ?:. Apabila Java mengembalikan null, nama?.uppercase() bernilai null dan nama?.length ?: 0 bernilai 0, tanpa NPE sama sekali. Perbedaan dengan bagian sebelumnya terletak pada waktu penemuan masalah. Pilihan tipe yang eksplisit memaksa pemeriksaan dilakukan pada waktu kompilasi, sedangkan platform type yang dibiarkan mengalir memindahkan pemeriksaan ke runtime.
Ringkasan#
- Kelas Java dipanggil dari Kotlin tanpa pembungkus, dan pasangan getter-setter Java hadir sebagai properti sintetis.
- Nilai dari API Java tanpa anotasi bertipe platform type yang dapat diberikan ke
StringmaupunString?, tanpa pemeriksaan compiler, sehingga NPE berpindah ke runtime. - Anotasi
@Nullabledan@NotNullpada kode Java mengubah platform type menjadi kontrak yang diperiksa pada waktu kompilasi. - Menyatakan
String?atauStringsecara eksplisit pada titik penerimaan nilai Java memindahkan pemeriksaan null ke waktu kompilasi.
Referensi#
- Calling Java from Kotlin — JetBrains — Dokumentasi resmi pemanggilan Java dari Kotlin, termasuk null safety dan platform type, pemetaan getter dan setter, serta anotasi nullability yang dikenali compiler.