Kompilator Kotlin menamai file class berdasarkan nama file sumber dengan akhiran Kt (mis. Util.kt → UtilKt). Method di dalamnya juga menambah suffix tertentu. Seringkali kita ingin mengubah nama ini agar lebih bersih di sisi Java — mis. Util.jumlahkan(3, 5) alih-alih UtilKt.jumlahkan(3, 5). Lebih kritis lagi, saat dua function Kotlin memiliki nama parameter generik yang setelah type erasure menjadi signature Java identik, kompilasi gagal. Anotasi @JvmName mengatasi kedua masalah ini. Bab ini membahas tiga penggunaannya: @file:JvmName, @JvmName pada function, dan disambiguasi type erasure.
Tiga Skenario Penggunaan #
| Skenario | Anotasi | Dampak |
|---|---|---|
| Ubah nama file class | @file:JvmName("Util") di awal file | FileNameKt → Util |
| Ubah nama method Java | @JvmName("custom") pada function | namaAsli() → custom() |
| Disambiguasi type erasure | @JvmName pada salah satu overload | Hindari platform declaration clash |
| Ubah nama property accessor | @JvmName("customGet") pada getter | getX() → customGet() |
@JvmName tidak mengubah nama di sisi Kotlin — kode Kotlin tetap memakai nama asli. Hanya bytecode Java yang terpengaruh.
Contoh @file:JvmName #
Untuk mengubah nama file class, tambahkan @file:JvmName("NamaBaru") di baris pertama file (sebelum deklarasi package/import):
@file:JvmName("UtilMatematika")
package com.example.util
fun jumlahkan(a: Int, b: Int): Int = a + b
fun kalikan(a: Int, b: Int): Int = a * bTanpa anotasi, kode Java memanggil UtilKt.jumlahkan(3, 5). Dengan anotasi, kode Java memanggil UtilMatematika.jumlahkan(3, 5) — lebih bersih dan idiomatik. Beberapa file Kotlin juga dapat berbagi satu nama file class dengan @file:JvmName("Util") di setiap file, lalu @file:JvmMultifileClass untuk menggabungkan.
Contoh Lengkap: @JvmName pada Function, Property, Extension #
Kode berikut mendemonstrasikan @JvmName pada function overload, extension function, dan property accessor:
// Demonstrasi @JvmName: mengubah nama JVM dari function atau accessor.
//
// Tiga kasus penggunaan utama:
// 1. @file:JvmName("Util") -> ubah nama file class (demonstrasi di index.md)
// 2. @JvmName("custom") pada function -> ubah nama method di bytecode
// 3. @JvmName untuk disambiguasi type erasure (List<String> vs List<Int>)
//
// Penting: @JvmName TIDAK mengubah nama di sisi Kotlin — Kotlin tetap
// memakai nama asli. Anotasi hanya berdampak pada bytecode Java.
// 1. Top-level function tanpa @file:JvmName -> class default Jvm_nameKt
fun jumlahkan(a: Int, b: Int): Int = a + b
fun kalikan(a: Int, b: Int): Int = a * b
// 2. @JvmName pada function untuk mengubah nama method Java
fun hitungGajiPokok(jamKerja: Int, tarifPerJam: Int): Int = jamKerja * tarifPerJam
@JvmName("hitungGajiPokokBersih")
fun hitungGajiPokok(jamKerja: Int, tarifPerJam: Int, potongan: Int): Int {
return jamKerja * tarifPerJam - potongan
}
// 3. @JvmName untuk disambiguasi type erasure
// Setelah type erasure, List<String>.prosesList dan List<Int>.prosesList
// memiliki signature yang sama: prosesList(List). @JvmName mengatasi konflik.
@JvmName("prosesListString")
fun prosesList(daftar: List<String>): String = daftar.joinToString("|")
@JvmName("prosesListInt")
fun prosesList(daftar: List<Int>): Int = daftar.sum()
// 4. @JvmName pada extension function
@JvmName("rataRata")
fun List<Int>.average(): Double = if (isEmpty()) 0.0 else sum().toDouble() / size
// 5. @JvmName pada property getter (untuk nama yang berbeda dari properti)
class Akun {
private var _saldo: Int = 0
var saldo: Int
@JvmName("getSaldoSekarang") get() = _saldo
@JvmName("setSaldoSekarang") set(value) {
require(value >= 0) { "Saldo tidak boleh negatif" }
_saldo = value
}
}
fun main() {
// 1. Top-level function (kelas file default: Jvm_nameKt)
println("=== Top-level function ===")
println("jumlahkan(3, 5) : ${jumlahkan(3, 5)}")
println("kalikan(3, 5) : ${kalikan(3, 5)}")
// Java (tanpa @file:JvmName):
// Jvm_nameKt.jumlahkan(3, 5)
// Jvm_nameKt.kalikan(3, 5)
// Dengan @file:JvmName("UtilMatematika") (di awal file):
// UtilMatematika.jumlahkan(3, 5)
// UtilMatematika.kalikan(3, 5)
// 2. @JvmName pada function (overload dengan nama Java berbeda)
println()
println("=== @JvmName pada function ===")
println("hitungGajiPokok(160, 50000) : ${hitungGajiPokok(160, 50000)}")
println("hitungGajiPokok(160, 50000, 100000) : ${hitungGajiPokok(160, 50000, 100000)}")
// Java:
// Jvm_nameKt.hitungGajiPokok(160, 50000) <-- 2-arg
// Jvm_nameKt.hitungGajiPokokBersih(160, 50000, 100000) <-- 3-arg @JvmName
// 3. Disambiguasi type erasure
println()
println("=== Disambiguasi type erasure ===")
val strList = listOf("Budi", "Siti", "Dewi")
val intList = listOf(10, 20, 30)
println("prosesList(strList) : ${prosesList(strList)}")
println("prosesList(intList) : ${prosesList(intList)}")
// Java:
// Jvm_nameKt.prosesListString(list) <-- List<String>
// Jvm_nameKt.prosesListInt(list) <-- List<Integer>
// 4. @JvmName pada extension function
println()
println("=== @JvmName pada extension ===")
val angka = listOf(10, 20, 30, 40)
println("angka.average() : ${angka.average()}")
// Java: Jvm_nameKt.rataRata(list)
// 5. @JvmName pada property accessor
println()
println("=== @JvmName pada property ===")
val akun = Akun()
akun.saldo = 100000
println("akun.saldo : ${akun.saldo}")
// Java:
// akun.getSaldoSekarang()
// akun.setSaldoSekarang(100000)
// Tanpa @JvmName: akun.getSaldo() / akun.setSaldo(int)
}Output yang Dihasilkan:
=== Top-level function ===
jumlahkan(3, 5) : 8
kalikan(3, 5) : 15
=== @JvmName pada function ===
hitungGajiPokok(160, 50000) : 8000000
hitungGajiPokok(160, 50000, 100000) : 7900000
=== Disambiguasi type erasure ===
prosesList(strList) : Budi|Siti|Dewi
prosesList(intList) : 60
=== @JvmName pada extension ===
angka.average() : 25.0
=== @JvmName pada property ===
akun.saldo : 100000Mari kita telaah baris-baris kode di atas:
- Baris 16-19 (
@file:JvmNamedi komentar): dalam file runnable ini kita tidak memakai@file:JvmNameagar file class tetapJvm_nameKt(sesuai prediksi script otomatis). Namun pada library nyata, tambahkan@file:JvmName("UtilMatematika")di baris pertama file agar kode Java memanggilUtilMatematika.jumlahkan(...)alih-alihJvm_nameKt.jumlahkan(...). - Baris 23-25 (
fun hitungGajiPokok(jamKerja, tarifPerJam)2-arg): function 2-argumen tanpa@JvmName. Bytecode Java memiliki methodhitungGajiPokok(int, int). - Baris 27-30 (
@JvmName("hitungGajiPokokBersih") fun hitungGajiPokok(jamKerja, tarifPerJam, potongan)): function 3-argumen dengan@JvmNamemengubah nama Java menjadihitungGajiPokokBersih. Di sisi Kotlin, kedua function tetap dipanggil sebagaihitungGajiPokok(...)karena kompilator Kotlin menerjemahkan berdasarkan parameter. Di bytecode Java, keduanya menjadi method berbeda (hitungGajiPokokvshitungGajiPokokBersih), menghindari konflik signature. - Baris 36-39 (
@JvmName("prosesListString")dan@JvmName("prosesListInt")): kedua function Kotlin memiliki nama samaprosesListnamun parameter berbeda (List<String>vsList<Int>). Tanpa@JvmName, kompilasi gagal dengan errorplatform declaration clashkarena setelah type erasure di JVM, kedua function memiliki signature identikprosesList(List).@JvmNamemengatasi dengan memberi nama bytecode berbeda. - Baris 42-43 (
@JvmName("rataRata") fun List<Int>.average()): extension function di bytecode Java menjadi static methodJvm_nameKt.average(List).@JvmName("rataRata")mengubah menjadiJvm_nameKt.rataRata(List)— lebih idiomatik di sisi Java. - Baris 47-55 (
Akun.saldodengan@JvmNamepada getter/setter): propertysaldodi Kotlin tetap dipanggil sebagaiakun.saldo. Di bytecode Java, getter menjadigetSaldoSekarang()dan settersetSaldoSekarang(int)— bukan defaultgetSaldo/setSaldo. Ini berguna saat kita ingin mengekspos API Java dengan nama yang berbeda dari nama properti internal Kotlin.
Pola @file:JvmName dan @file:JvmMultifileClass #
Untuk library besar yang tersebar di beberapa file Kotlin namun ingin diekspos sebagai satu class Java, gunakan kombinasi @file:JvmName + @file:JvmMultifileClass:
// File: MathUtil.kt
@file:JvmName("Util")
@file:JvmMultifileClass
package com.example.util
fun jumlahkan(a: Int, b: Int): Int = a + b
// File: StringUtil.kt
@file:JvmName("Util")
@file:JvmMultifileClass
package com.example.util
fun balikString(s: String): String = s.reversed()Kedua file di atas menghasilkan satu class Java Util yang berisi Util.jumlahkan(...) dan Util.balikString(...). Strategi ini sering dipakai di standard library Kotlin (mis. kotlin.collections.CollectionsKt).
Referensi Lanjutan #
- Calling Java from Kotlin
— dokumentasi Java interop Kotlin untuk anotasi
@JvmName - Calling Kotlin from Java — panduan mengekspos nama method Kotlin ke pemanggil Java
Selanjutnya #
Topik interop berikutnya adalah koleksi. Tipe List Kotlin bukan java.util.List — keduanya berinteraksi dengan transformasi otomatis yang memunculkan pertimbangan khusus. Bab Java Collections Interoperability akan membahas perbedaan read-only/mutable dan jebakan platform type pada elemen koleksi.