Interoperabilitas tidak hanya satu arah. Kode Java juga dapat memanggil kode Kotlin — namun karena Java tidak mengenal top-level function, default argumen, atau companion object, kompilator Kotlin harus menerjemahkan semua itu menjadi konstruksi Java yang setara. Bab ini menjelaskan tiga aturan translasi: top-level function menjadi FileNameKt.methodName(), companion object menjadi ClassName.Companion (atau @JvmStatic untuk static sejati), dan default argumen memerlukan @JvmOverloads agar Java dapat memanggil tanpa semua argumen.
Tiga Aturan Translasi #
| Konstruk Kotlin | Padanan Java Default | Anotasi yang Membantu |
|---|---|---|
Top-level function fun sapa(...) | FileNameKt.sapa(...) | @file:JvmName("Util") (chapter @JvmName) |
companion object | ClassName.Companion.INSTANCE.method() | @JvmStatic |
Default argumen fun f(a: Int, b: Int = 0) | hanya satu signature f(int,int) | @JvmOverloads menghasilkan beberapa overload |
Top-level property val X = ... | FileNameKt.getX() | @JvmField (chapter @JvmField dan @JvmStatic) |
Kode Java yang memanggil Kotlin harus mematuhi translasi ini. Bab ini berfokus pada tiga aturan yang paling sering dijumpai di proyek nyata.
Contoh Lengkap: Top-Level, Object, Companion, Default Arg #
Kode berikut mendefinisikan lima konstruk Kotlin dan menampilkan padanan Java yang akan dipanggil dari sisi Java:
// Demonstrasi bagaimana kode Kotlin dilihat dari sisi Java:
// - Top-level function menjadi static method pada file class FileNameKt
// - @JvmStatic menghasilkan static method sejati pada companion object
// - @JvmOverloads menghasilkan beberapa overload untuk default arg
// 1. Top-level function: di Java menjadi UtilKt.sapa("Budi")
fun sapa(nama: String): String = "Halo, $nama!"
// 2. Top-level function dengan default arg: perlu @JvmOverloads agar Java bisa panggil tanpa arg ke-2
@JvmOverloads
fun hitungDiskon(harga: Int, persen: Int = 10, minimum: Int = 0): Int {
val diskon = (harga * persen) / 100
return if (diskon > minimum) diskon else minimum
}
// 3. Object singleton: di Java menjadi Util.INSTANCE
object Penanda {
const val VERSI: String = "1.0.0"
fun tandaVersi(): String = "v$VERSI"
}
// 4. Companion object dengan @JvmStatic agar dapat dipanggil sebagai static method sejati
class Matematika {
companion object {
@JvmStatic
fun kuadrat(x: Int): Int = x * x
@JvmStatic
val KONSTANTA_PI: Double = 3.14
}
}
// 5. Top-level property: di Java menjadi UtilKt.getNamaAplikasi()
const val NAMA_APLIKASI: String = "BelajarKotlin"
fun main() {
// 1. Top-level function
println("=== Top-level function ===")
println("sapa(\"Budi\") : ${sapa("Budi")}")
// 2. Default argumen (Kotlin bisa panggil tanpa @JvmOverloads)
println()
println("=== Default argumen ===")
println("hitungDiskon(100000) : ${hitungDiskon(100000)}")
println("hitungDiskon(100000, 20) : ${hitungDiskon(100000, 20)}")
println("hitungDiskon(100000, 20, 5000) : ${hitungDiskon(100000, 20, 5000)}")
// 3. Object singleton
println()
println("=== Object singleton ===")
println("Penanda.VERSI : ${Penanda.VERSI}")
println("Penanda.tandaVersi() : ${Penanda.tandaVersi()}")
// 4. Companion object dengan @JvmStatic
println()
println("=== Companion + @JvmStatic ===")
println("Matematika.kuadrat(5) : ${Matematika.kuadrat(5)}")
println("Matematika.KONSTANTA_PI : ${Matematika.KONSTANTA_PI}")
// 5. Top-level property
println()
println("=== Top-level property ===")
println("NAMA_APLIKASI : $NAMA_APLIKASI")
// Simulasi pemanggilan dari sisi Java (yang akan memakai):
// UtilKt.sapa("Budi")
// UtilKt.hitungDiskon(100000)
// UtilKt.hitungDiskon(100000, 20)
// UtilKt.hitungDiskon(100000, 20, 5000)
// Penanda.INSTANCE.tandaVersi()
// Matematika.kuadrat(5) <-- @JvmStatic, no INSTANCE
// UtilKt.getNAMA_APLIKASI()
println()
println("=== Padanan Java ===")
println("// Sisi Java yang memanggil:")
println("// Calling_kotlin_from_javaKt.sapa(\"Budi\")")
println("// Calling_kotlin_from_javaKt.hitungDiskon(100000)")
println("// Calling_kotlin_from_javaKt.hitungDiskon(100000, 20)")
println("// Calling_kotlin_from_javaKt.hitungDiskon(100000, 20, 5000)")
println("// Penanda.INSTANCE.tandaVersi()")
println("// Matematika.kuadrat(5) <-- @JvmStatic, tanpa INSTANCE")
println("// Calling_kotlin_from_javaKt.getNAMA_APLIKASI()")
}Output yang Dihasilkan:
=== Top-level function ===
sapa("Budi") : Halo, Budi!
=== Default argumen ===
hitungDiskon(100000) : 10000
hitungDiskon(100000, 20) : 20000
hitungDiskon(100000, 20, 5000) : 20000
=== Object singleton ===
Penanda.VERSI : 1.0.0
Penanda.tandaVersi() : v1.0.0
=== Companion + @JvmStatic ===
Matematika.kuadrat(5) : 25
Matematika.KONSTANTA_PI : 3.14
=== Top-level property ===
NAMA_APLIKASI : BelajarKotlin
=== Padanan Java ===
// Sisi Java yang memanggil:
// Calling_kotlin_from_javaKt.sapa("Budi")
// Calling_kotlin_from_javaKt.hitungDiskon(100000)
// Calling_kotlin_from_javaKt.hitungDiskon(100000, 20)
// Calling_kotlin_from_javaKt.hitungDiskon(100000, 20, 5000)
// Penanda.INSTANCE.tandaVersi()
// Matematika.kuadrat(5) <-- @JvmStatic, tanpa INSTANCE
// Calling_kotlin_from_javaKt.getNAMA_APLIKASI()Mari kita telaah baris-baris kode di atas:
- Baris 7 (
fun sapa(nama: String): String): top-level function. Kompilator membuat class bernamaCalling_kotlin_from_javaKt(berdasarkan nama filecalling_kotlin.ktdengan kapitalisasi pertama, underscore dipertahankan, akhiranKt). Sisi Java memanggilCalling_kotlin_from_javaKt.sapa("Budi")sebagai static method. - Baris 11 (
@JvmOverloads fun hitungDiskon(...)): tanpa@JvmOverloads, Java hanya melihat satu signaturehitungDiskon(int,int,int)dan tidak dapat memakai default argumen. Anotasi ini menghasilkan tiga overload Java:hitungDiskon(int),hitungDiskon(int,int), danhitungDiskon(int,int,int). - Baris 18-21 (
object Penanda):objectdi Kotlin menjadi singleton class di Java. Sisi Java mengakses instance lewatPenanda.INSTANCE. MethodtandaVersi()dipanggil sebagaiPenanda.INSTANCE.tandaVersi(). KonstantaVERSI(ditandaiconst) menjadipublic static finalsejati. - Baris 26-31 (
companion objectdengan@JvmStatic): tanpa@JvmStatic, Java harus menulisMatematika.Companion.kuadrat(5). Dengan@JvmStatic, kompilator membuat static method sejatiMatematika.kuadrat(5)tanpa perantaraCompanion. FieldKONSTANTA_PIyang ditandai@JvmStaticjuga menjadi static accessor sejati. - Baris 35 (
const val NAMA_APLIKASI): kata kunciconstmembuat properti menjadipublic static final String. Sisi Java membacaCalling_kotlin_from_javaKt.NAMA_APLIKASI(bukan getter, karenaconstmembuat field langsung). Tanpaconst, aksesnya via gettergetNAMA_APLIKASI(). - Baris 58-66 (print padanan Java): komentar ini menjelaskan translasi yang terjadi di bytecode. Pemanggilan dari Java persis seperti komentar menunjukkan — kompilator Java tidak tahu bahwa kode aslinya adalah Kotlin.
Strategi Anotasi untuk Library Kotlin yang Dipakai Java #
Bila kita membangun library Kotlin yang akan dipakai dari Java, anotasi berikut sebaiknya menjadi default:
| Kasus | Anotasi | Manfaat |
|---|---|---|
| Top-level function dipanggil Java | @file:JvmName("Util") | Nama pendek & deskriptif |
| Method di companion object dipanggil Java | @JvmStatic | Panggilan langsung tanpa .Companion |
| Function dengan default argumen | @JvmOverloads | Java dapat panggil tanpa semua arg |
| Konstanta publik | const val (atau @JvmField) | Akses langsung tanpa getter |
Tanpa anotasi ini, kode Java yang memanggil Kotlin akan tampak berantakan dan tidak idiomatik. Library yang baik untuk konsumsi Java harus membungkus detail ini di level API publik.
Referensi Lanjutan #
- Calling Kotlin from Java — panduan resmi Kotlin untuk mengekspos kode Kotlin ke pemanggil Java
Selanjutnya #
Top-level function Kotlin yang dipanggil dari Java mengembalikan tipe yang tidak Kotlin ketahui nullability-nya. Konsep platform types inilah yang menjadi sumber NullPointerException tersekat di kode hybrid Java+Kotlin. Bab Platform Types akan membahasnya tuntas.