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 #

SkenarioAnotasiDampak
Ubah nama file class@file:JvmName("Util") di awal fileFileNameKt → Util
Ubah nama method Java@JvmName("custom") pada functionnamaAsli() → custom()
Disambiguasi type erasure@JvmName pada salah satu overloadHindari platform declaration clash
Ubah nama property accessor@JvmName("customGet") pada gettergetX() → 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):

kotlin Pola: @file:JvmName
@file:JvmName("UtilMatematika")

package com.example.util

fun jumlahkan(a: Int, b: Int): Int = a + b
fun kalikan(a: Int, b: Int): Int = a * b

Tanpa 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:

kotlin
// 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:

text
=== 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      : 100000

Mari kita telaah baris-baris kode di atas:

  1. Baris 16-19 (@file:JvmName di komentar): dalam file runnable ini kita tidak memakai @file:JvmName agar file class tetap Jvm_nameKt (sesuai prediksi script otomatis). Namun pada library nyata, tambahkan @file:JvmName("UtilMatematika") di baris pertama file agar kode Java memanggil UtilMatematika.jumlahkan(...) alih-alih Jvm_nameKt.jumlahkan(...).
  2. Baris 23-25 (fun hitungGajiPokok(jamKerja, tarifPerJam) 2-arg): function 2-argumen tanpa @JvmName. Bytecode Java memiliki method hitungGajiPokok(int, int).
  3. Baris 27-30 (@JvmName("hitungGajiPokokBersih") fun hitungGajiPokok(jamKerja, tarifPerJam, potongan)): function 3-argumen dengan @JvmName mengubah nama Java menjadi hitungGajiPokokBersih. Di sisi Kotlin, kedua function tetap dipanggil sebagai hitungGajiPokok(...) karena kompilator Kotlin menerjemahkan berdasarkan parameter. Di bytecode Java, keduanya menjadi method berbeda (hitungGajiPokok vs hitungGajiPokokBersih), menghindari konflik signature.
  4. Baris 36-39 (@JvmName("prosesListString") dan @JvmName("prosesListInt")): kedua function Kotlin memiliki nama sama prosesList namun parameter berbeda (List<String> vs List<Int>). Tanpa @JvmName, kompilasi gagal dengan error platform declaration clash karena setelah type erasure di JVM, kedua function memiliki signature identik prosesList(List). @JvmName mengatasi dengan memberi nama bytecode berbeda.
  5. Baris 42-43 (@JvmName("rataRata") fun List<Int>.average()): extension function di bytecode Java menjadi static method Jvm_nameKt.average(List). @JvmName("rataRata") mengubah menjadi Jvm_nameKt.rataRata(List) — lebih idiomatik di sisi Java.
  6. Baris 47-55 (Akun.saldo dengan @JvmName pada getter/setter): property saldo di Kotlin tetap dipanggil sebagai akun.saldo. Di bytecode Java, getter menjadi getSaldoSekarang() dan setter setSaldoSekarang(int) — bukan default getSaldo/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:

kotlin Pola: multi-file class
// 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 #

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.