Bab sebelumnya memperkenalkan @JvmStatic dan @JvmField sebagai anotasi untuk menghasilkan static sejati. Bab ini membedah keduanya secara mendalam: kapan harus memilih satu dibanding yang lain, perbedaan bytecode yang dihasilkan, dan jebakan umum yang muncul saat pemakaian. Pemahaman mendalam terhadap kedua anotasi ini penting karena kesalahan pemilihan menghasilkan API Java yang verbose atau bahkan tidak idiomatik.
Perbedaan Inti @JvmField vs @JvmStatic #
| Anotasi | Berlaku Pada | Efek Bytecode |
|---|---|---|
@JvmField | Property val/var | Field public (static bila di companion/object), tanpa getter/setter |
@JvmStatic | Method di companion/object | Static method sejati yang didelegasikan ke Companion.INSTANCE |
const val | Top-level / companion val (primitif/String) | Field public static final inline |
Perbedaan kunci: @JvmField menghapus getter/setter, @JvmStatic menambah static method delegasi. Keduanya saling melengkapi — bisa dipakai bersamaan pada satu companion object.
Lima Konfigurasi yang Umum #
Kode berikut mendefinisikan lima variasi properti/method di companion object, lalu menampilkan padanan Java yang dihasilkan untuk masing-masing:
// Demonstrasi @JvmField vs @JvmStatic: perbedaan, kapan dipakai, dan efek bytecode
//
// @JvmField: menghasilkan field public (static bila di companion/object), tanpa getter/setter
// @JvmStatic: menghasilkan static method sejati di bytecode Java
//
// Perbandingan konkrit:
// - val biasa -> getX() dipanggil
// - @JvmField val -> field X dibaca langsung, tanpa getX()
// - fun biasa di comp -> Companion.fun() dipanggil
// - @JvmStatic fun -> Class.fun() dipanggil
class MatematikaDasar {
companion object {
// A: val biasa -> Java: MatematikaDasar.Companion.getPI()
val PI_TANPA_JVMFIELD: Double = 3.14159
// B: @JvmField val -> Java: MatematikaDasar.PI (field langsung)
@JvmField
val PI_DENGAN_JVMFIELD: Double = 3.14159
// C: const val -> Java: MatematikaDasar.PI (public static final, inline)
const val NAMA: String = "MatematikaDasar"
// D: fun biasa -> Java: MatematikaDasar.Companion.kuadratTanpa(5)
fun kuadratTanpa(x: Int): Int = x * x
// E: @JvmStatic fun -> Java: MatematikaDasar.kuadratStatic(5)
@JvmStatic
fun kuadratStatic(x: Int): Int = x * x
}
}
// @JvmField pada top-level property -> static field di FileNameKt
@JvmField
val MAKS_PRODUK: Int = 100
// @JvmField pada instance property (non-companion) -> field public biasa di instance
class KonfigurasiApp {
@JvmField
val versi: String = "1.0.0"
@JvmField
var hitungPengguna: Int = 0
}
// @JvmField pada object singleton -> static field
object PenyusunUrl {
@JvmField
val HOST: String = "api.example.com"
@JvmStatic
fun bangun(path: String): String = "https://$HOST/$path"
}
fun main() {
// A vs B: val biasa vs @JvmField val di companion
println("=== A vs B: companion val ===")
println("PI_TANPA_JVMFIELD : ${MatematikaDasar.PI_TANPA_JVMFIELD}")
println("PI_DENGAN_JVMFIELD : ${MatematikaDasar.PI_DENGAN_JVMFIELD}")
println("// Java:")
println("// MatematikaDasar.Companion.getPI_TANPA_JVMFIELD()")
println("// MatematikaDasar.PI_DENGAN_JVMFIELD (field langsung)")
// C: const val
println()
println("=== C: const val ===")
println("NAMA : ${MatematikaDasar.NAMA}")
println("// Java: MatematikaDasar.NAMA (public static final inline)")
// D vs E: fun biasa vs @JvmStatic fun
println()
println("=== D vs E: companion fun ===")
println("kuadratTanpa(5) : ${MatematikaDasar.kuadratTanpa(5)}")
println("kuadratStatic(5) : ${MatematikaDasar.kuadratStatic(5)}")
println("// Java:")
println("// MatematikaDasar.Companion.kuadratTanpa(5)")
println("// MatematikaDasar.kuadratStatic(5) [static method]")
// @JvmField pada top-level property
println()
println("=== @JvmField top-level ===")
println("MAKS_PRODUK : $MAKS_PRODUK")
println("// Java: Jvm_field_dan_jvm_staticKt.MAKS_PRODUK (static field)")
// @JvmField pada instance property
println()
println("=== @JvmField instance ===")
val app = KonfigurasiApp()
println("app.versi : ${app.versi}")
println("app.hitungPengguna : ${app.hitungPengguna}")
app.hitungPengguna = 5
println("Setelah set: app.hitungPengguna = ${app.hitungPengguna}")
println("// Java:")
println("// app.versi (field public)")
println("// app.hitungPengguna = 5 (field public, assignable)")
// @JvmField + @JvmStatic pada object singleton
println()
println("=== Object singleton ===")
println("PenyusunUrl.HOST : ${PenyusunUrl.HOST}")
println("PenyusunUrl.bangun(\"v1/users\") : ${PenyusunUrl.bangun("v1/users")}")
println("// Java:")
println("// PenyusunUrl.HOST (static field)")
println("// PenyusunUrl.bangun(\"v1/users\") [static method]")
}Output yang Dihasilkan:
=== A vs B: companion val ===
PI_TANPA_JVMFIELD : 3.14159
PI_DENGAN_JVMFIELD : 3.14159
// Java:
// MatematikaDasar.Companion.getPI_TANPA_JVMFIELD()
// MatematikaDasar.PI_DENGAN_JVMFIELD (field langsung)
=== C: const val ===
NAMA : MatematikaDasar
// Java: MatematikaDasar.NAMA (public static final inline)
=== D vs E: companion fun ===
kuadratTanpa(5) : 25
kuadratStatic(5) : 25
// Java:
// MatematikaDasar.Companion.kuadratTanpa(5)
// MatematikaDasar.kuadratStatic(5) [static method]
=== @JvmField top-level ===
MAKS_PRODUK : 100
// Java: Jvm_field_dan_jvm_staticKt.MAKS_PRODUK (static field)
=== @JvmField instance ===
app.versi : 1.0.0
app.hitungPengguna : 0
Setelah set: app.hitungPengguna = 5
// Java:
// app.versi (field public)
// app.hitungPengguna = 5 (field public, assignable)
=== Object singleton ===
PenyusunUrl.HOST : api.example.com
PenyusunUrl.bangun("v1/users") : https://api.example.com/v1/users
// Java:
// PenyusunUrl.HOST (static field)
// PenyusunUrl.bangun("v1/users") [static method]Mari kita telaah baris-baris kode di atas:
- Baris 14 (
val PI_TANPA_JVMFIELDtanpa anotasi): propertivalbiasa dicompanion objectmenghasilkan gettergetPI_TANPA_JVMFIELD()di instanceCompanion. Sisi Java memanggilMatematikaDasar.Companion.getPI_TANPA_JVMFIELD()— verbose. Sisi Kotlin tetap menulisMatematikaDasar.PI_TANPA_JVMFIELD(kompilator menerjemahkan). - Baris 18 (
@JvmField val PI_DENGAN_JVMFIELD): anotasi ini menghapus getter, menghasilkan field public static final di bytecode Java. Sisi Java membacaMatematikaDasar.PI_DENGAN_JVMFIELDlangsung tanpa perantaraCompanionatau method getter. Inilah idiom yang dipakai library Java sepertiMath.PI. - Baris 22 (
const val NAMA):constadalah modifier bawaan Kotlin untuk properti compile-time constant (harus primitif atau String). Hasilnya fieldpublic static finalyang dapat di-inline oleh kompilator Java di lokasi pemanggilan — lebih efisien daripada@JvmFielduntuk konstanta literal. - Baris 25-26 (
fun kuadratTanpa(x)tanpa anotasi): method dicompanion objecttanpa@JvmStaticmenjadi method instance padaCompanion. Sisi Java harus menulisMatematikaDasar.Companion.kuadratTanpa(5)— sangat tidak idiomatik. - Baris 29 (
@JvmStatic fun kuadratStatic(x)): anotasi menghasilkan static method sejati di classMatematikaDasar. Sisi Java memanggilMatematikaDasar.kuadratStatic(5)langsung. Kompilator tetap mempertahankan methodCompanion.kuadratStaticuntuk konsistensi internal, namun idiom Java memakai static sejati. - Baris 35 (
@JvmField val MAKS_PRODUKtop-level): anotasi pada top-level property menghasilkan fieldpublic static finaldi classJvm_field_dan_jvm_staticKt. Sisi Java membacaJvm_field_dan_jvm_staticKt.MAKS_PRODUKlangsung. - Baris 39-44 (
KonfigurasiAppdengan@JvmField var): pada instance (non-companion),@JvmFieldmembuat field public langsung. Bila propertivar, field menjadi assignable dari luar class. Ini menghapus enkapsulasi — hanya pakai bila kita memang ingin field public mutable (jarang dianjurkan). - Baris 49-55 (
object PenyusunUrl): kombinasi@JvmField val HOST+@JvmStatic fun bangun()diobjectsingleton menghasilkan padanan Java yang sangat bersih. Sisi Java memanggilPenyusunUrl.bangun("v1/users")dan membacaPenyusunUrl.HOSTtanpa perantaraINSTANCE.
Jebakan Umum #
| Jebakan | Akibat | Solusi |
|---|---|---|
@JvmField pada var di instance | Field public mutable tanpa validasi | Pakai var biasa + custom setter |
@JvmField pada properti dengan custom getter | Kompilasi gagal — @JvmField melarang accessor custom | Hapus custom getter atau hapus @JvmField |
@JvmStatic pada top-level function | Tidak ada efek — @JvmStatic hanya untuk companion/object | Pakai @file:JvmName("Util") saja |
const val pada tipe non-primitif | Kompilasi gagal — const hanya primitif/String | Pakai @JvmField val untuk objek |
@JvmStatic pada var property | Tidak ada efek — @JvmStatic hanya untuk method | Pakai @JvmField untuk field |
Referensi Lanjutan #
- Static fields and methods
— panduan
@JvmFielddan@JvmStaticuntuk eksposisi field/method statis - Calling Java from Kotlin — dokumentasi Java interop Kotlin untuk anotasi JVM
Selanjutnya #
Selain @JvmStatic dan @JvmField, ada anotasi @JvmOverloads yang menghasilkan beberapa overload Java untuk default argumen Kotlin. Bab berikutnya membahas cara kerja dan strategi pemakaiannya.