Mencampur Basis Kode

Desain modul interop dan batas yang aman antarbahasa.

Daftar isi

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.

  1. Modul utilitas dan fungsi pengujian dipindahkan lebih dulu, karena ruang lingkupnya kecil dan dampak kegagalannya rendah.
  2. Modul dengan API paling stabil menyusul, disertai anotasi nullability agar kontraknya jelas bagi kedua sisi.
  3. 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 batasBentuk di KotlinDampak bagi pemanggil Java
Nama kelas fungsi top-level@file:JvmName("NamaBersih")Kelas bernama NamaBersih, tanpa akhiran Kt
Fungsi pada companion object@JvmStaticPemanggilan Kelas.fungsi() tanpa menyebut Companion
Argumen bawaan@JvmOverloadsOverload tersedia untuk setiap parameter bawaan yang dilepas
Nilai yang wajib adaParameter bertipe StringPelanggaran kontrak terdeteksi sebagai NPE di batas
Nilai yang boleh kosongParameter 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.

kotlin penghitung.kt
@file:JvmName("PenghitungKata")

// Menghitung jumlah kata pada satu baris teks
fun hitungKata(teks: String): Int =
    teks.trim().split(Regex("\\s+")).count { it.isNotEmpty() }
java Laporan.java
// 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());
        }
    }
}
bash
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:." Laporan
text Keluaran
4
Tertangkap: Parameter specified as non-null is null: method PenghitungKata.hitungKata, parameter teks

Tanpa 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 @JvmOverloads merapikan 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#