Proyek JVM berukuran sedang ke atas jarang berisi satu bahasa saja, dan kombinasi Java dengan Kotlin adalah bentuk campuran yang paling umum. Kedua bahasa berbagi runtime dan bytecode yang sama, sehingga pemanggilan antarbahasa hanyalah urusan kompilasi dan classpath. Topik ini membahas urutan adopsi yang lazim, keputusan desain pada batas antarbahasa, dan demo kecil berupa satu modul Kotlin yang dipakai langsung oleh kelas Java.
Dua bahasa pada satu runtime#
Interoperabilitas Java dan Kotlin bekerja karena keduanya berhenti di artefak yang sama, yaitu berkas kelas JVM. Ketika modul Kotlin dipanggil dari Java, yang dilihat Java hanyalah kelas hasil kompilasi, bukan kode sumber Kotlin. Konsekuensinya, campuran bahasa dapat dilakukan pada tingkat modul tanpa mengubah modul lain, karena yang disebut oleh pemanggil adalah nama kelas dan metode pada bytecode.
Urutan adopsi yang lazim diikuti proyek Java yang mulai memakai Kotlin adalah sebagai berikut.
- Modul utilitas dan fungsi pengujian dipindahkan lebih dulu, karena ruang lingkupnya kecil dan dampak kegagalannya rendah.
- Modul dengan API paling stabil menyusul, disertai anotasi nullability agar kontraknya jelas bagi kedua sisi.
- Modul yang sedang berkembang ditulis penuh dengan Kotlin, karena manfaat null safety paling terasa pada kode yang sering berubah.
Pola ini menjaga setiap tahap tetap dapat diuji secara terpisah. Perpindahan satu modul tidak memaksa perpindahan modul lain, dan kegagalan interop dapat dilacak pada satu batas saja.
Batas yang aman antarbahasa#
Batas antarbahasa adalah kumpulan kelas dan fungsi yang dipanggil dari bahasa lain. Batas yang baik bersifat tipis, eksplisit, dan sedikit. Semakin tipis batasnya, semakin sedikit permukaan yang harus dijaga dari perbedaan kebahasaan, seperti perbedaan penanganan null dan penamaan hasil kompilasi. Tabel berikut merangkum keputusan yang tersedia di sisi Kotlin agar pemanggil Java menerima bentuk yang rapi.
| Keputusan pada batas | Bentuk di Kotlin | Dampak bagi pemanggil Java |
|---|---|---|
| Nama kelas fungsi top-level | @file:JvmName("NamaBersih") | Kelas bernama NamaBersih, tanpa akhiran Kt |
| Fungsi pada companion object | @JvmStatic | Pemanggilan Kelas.fungsi() tanpa menyebut Companion |
| Argumen bawaan | @JvmOverloads | Overload tersedia untuk setiap parameter bawaan yang dilepas |
| Nilai yang wajib ada | Parameter bertipe String | Pelanggaran kontrak terdeteksi sebagai NPE di batas |
| Nilai yang boleh kosong | Parameter atau kembalian String? | Penanganan null menyertai kontrak sejak deklarasi |
Dua baris terakhir pada tabel menyangkut nullability, dan keduanya menuntut kejelasan dari sisi Kotlin. Platform type tidak boleh menyeberang batas tanpa dinyatakan, karena pemanggil Java tidak dapat menebak apakah null diterima atau tidak. Dengan tipe yang eksplisit, pelanggaran kontrak terjadi di batas modul dengan pesan yang jelas, bukan jauh di dalam kode pemanggil.
Demo kecil: modul Kotlin dipakai Java#
Demo berikut terdiri atas dua berkas. Berkas pertama adalah modul Kotlin penghitung kata yang memberi nama kelasnya sendiri melalui @file:JvmName, dan berkas kedua adalah kelas Java yang memanggilnya.
@file:JvmName("PenghitungKata")
// Menghitung jumlah kata pada satu baris teks
fun hitungKata(teks: String): Int =
teks.trim().split(Regex("\\s+")).count { it.isNotEmpty() }// tidak-dijalankan
// Modul Java memakai modul Kotlin melalui batas yang disepakati
public class Laporan {
public static void main(String[] args) {
System.out.println(PenghitungKata.hitungKata("arsip cadangan bulan November"));
try {
PenghitungKata.hitungKata(null);
} catch (NullPointerException e) {
System.out.println("Tertangkap: " + e.getMessage());
}
}
}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
# modul Kotlin dikompilasi menjadi jar
kotlinc penghitung.kt -d hitung.jar
# kelas Java dikompilasi dengan jar Kotlin pada classpath
javac -cp "hitung.jar:$STDLIB" Laporan.java
java -cp "hitung.jar:$STDLIB:." Laporan4
Tertangkap: Parameter specified as non-null is null: method PenghitungKata.hitungKata, parameter teksTanpa anotasi @file:JvmName, kelas hasil kompilasi akan bernama PenghitungKataKt sesuai nama berkasnya, dan kelas Java harus memanggil nama yang begitu. Dengan anotasi tersebut, nama kelasnya menjadi PenghitungKata seperti yang terlihat pada kode Java. Baris kedua keluaran memperlihatkan sisi lain dari batas yang tegas. Parameter teks dideklarasikan sebagai String non-null, sehingga compiler menyisipkan pemeriksaan null di awal fungsi. Pemanggilan dengan null dari Java tidak menjalar sampai ke dalam perhitungan, melainkan tertangkap sebagai NPE dengan pesan yang menyebut metode dan parameter yang melanggar.
Ringkasan#
- Java dan Kotlin berbagi runtime dan bytecode, sehingga campuran bahasa dapat dilakukan per modul tanpa mengubah modul lain.
- Batas antarbahasa yang aman bersifat tipis, eksplisit, dan dinyatakan nullability-nya, sedangkan platform type tidak dibiarkan menyeberang tanpa deklarasi tipe.
@file:JvmName,@JvmStatic, dan@JvmOverloadsmerapikan bentuk bytecode agar pemanggil Java tidak harus mengenal detail penamaan compiler.- Parameter non-null pada API Kotlin diperiksa saat pemanggilan, sehingga pelanggaran kontrak dari Java tertangkap di batas dengan pesan yang jelas.
Referensi#
- Calling Java from Kotlin — JetBrains — Sisi pertama interop, berisi aturan platform type dan anotasi nullability yang menjadi dasar desain batas.
- Calling Kotlin from Java — JetBrains — Sisi kedua interop, berisi anotasi penata nama dan bentuk bytecode yang dihasilkan untuk pemanggil Java.