Kotlin tidak memiliki keyword static — semua method dan field berada di dalam class atau top-level. Untuk memberikan perilaku statis (method/field yang dapat diakses tanpa instance), Kotlin memakai companion object atau object singleton. Kode Java yang memanggil Kotlin melihat Companion.INSTANCE.method() secara default. Anotasi @JvmStatic menghasilkan static method sejati yang dapat dipanggil langsung tanpa perantara. Bab ini menjelaskan translasi tersebut beserta dampaknya pada akses Java.
Tiga Konstruk Statis di Kotlin #
| Konstruk Kotlin | Default Java | Anotasi untuk Static Sejati |
|---|---|---|
companion object | ClassName.Companion.method() | @JvmStatic pada method |
Top-level object | ObjectName.INSTANCE.method() | @JvmStatic pada method |
Top-level val/var | FileNameKt.getX() | @JvmField atau const val |
Property di companion | ClassName.Companion.getX() | @JvmField untuk field langsung |
Default ini berfungsi penuh, namun sintaks Java yang dihasilkan tidak idiomatik. @JvmStatic dan @JvmField menghasilkan bytecode yang lebih bersih dan lebih konsisten dengan idiom Java.
Contoh Lengkap: Companion, Object, @JvmStatic, @JvmField #
Kode berikut mendefinisikan empat konfigurasi statis berbeda dan menunjukkan padanan Java yang dihasilkan:
// Demonstrasi static interop: companion object, @JvmStatic, @JvmField, object singleton
//
// - companion object Kotlin -> ClassName.Companion di Java
// - @JvmStatic -> static method sejati tanpa perantara Companion
// - @JvmField -> field public static final, tanpa getter
// - object -> singleton INSTANCE di Java
// 1. Companion object TANPA @JvmStatic
class VersiApp {
companion object {
val VERSI: String = "1.0.0"
fun namaLengkap(): String = "BelajarKotlin $VERSI"
}
}
// 2. Companion object DENGAN @JvmStatic dan @JvmField
class Konstanta {
companion object {
@JvmField
val PI: Double = 3.14159
@JvmField
val MAKSIMAL: Int = 100
@JvmStatic
fun namaKelas(): String = Konstanta::class.simpleName ?: "Konstanta"
@JvmStatic
fun kuadrat(x: Int): Int = x * x
}
}
// 3. Top-level object singleton: di Java menjadi Util.INSTANCE
object Pemetaan {
@JvmStatic
fun balikString(s: String): String = s.reversed()
@JvmField
val DEFAULT_PREFIX: String = "log_"
}
// 4. Data class dengan companion object factory
data class Produk(val id: Long, val nama: String) {
companion object {
@JvmStatic
fun kosong(): Produk = Produk(0, "")
@JvmStatic
fun dariBaris(baris: String): Produk {
val bagian = baris.split(",", limit = 2)
return if (bagian.size == 2) {
Produk(bagian[0].toLong(), bagian[1])
} else {
kosong()
}
}
}
}
fun main() {
// 1. Companion object tanpa @JvmStatic
println("=== Companion tanpa @JvmStatic ===")
println("VersiApp.VERSI : ${VersiApp.VERSI}")
println("VersiApp.namaLengkap() : ${VersiApp.namaLengkap()}")
// Dari Java:
// VersiApp.Companion.getVERSI()
// VersiApp.Companion.namaLengkap()
// 2. Companion object dengan @JvmStatic dan @JvmField
println()
println("=== Companion dengan @JvmStatic/@JvmField ===")
println("Konstanta.PI : ${Konstanta.PI}")
println("Konstanta.MAKSIMAL : ${Konstanta.MAKSIMAL}")
println("Konstanta.namaKelas() : ${Konstanta.namaKelas()}")
println("Konstanta.kuadrat(5) : ${Konstanta.kuadrat(5)}")
// Dari Java (static sejati):
// Konstanta.PI
// Konstanta.MAKSIMAL
// Konstanta.namaKelas()
// Konstanta.kuadrat(5)
// 3. Object singleton dengan @JvmStatic
println()
println("=== Object singleton ===")
println("Pemetaan.balikString(\"Halo\") : ${Pemetaan.balikString("Halo")}")
println("Pemetaan.DEFAULT_PREFIX : ${Pemetaan.DEFAULT_PREFIX}")
// Dari Java:
// Pemetaan.balikString("Halo") <-- static, tanpa INSTANCE
// Pemetaan.DEFAULT_PREFIX <-- static field, tanpa INSTANCE
// 4. Factory method di data class
println()
println("=== Factory method ===")
val p1 = Produk.kosong()
val p2 = Produk.dariBaris("42,Buku Kotlin")
println("Produk.kosong() : $p1")
println("Produk.dariBaris(...) : $p2")
// Dari Java:
// Produk.kosong()
// Produk.dariBaris("42,Buku Kotlin")
// 5. Perbandingan bytecode signature
println()
println("=== Perbandingan signature ===")
println("// VersiApp.namaLengkap() -> VersiApp.Companion.namaLengkap()")
println("// Konstanta.kuadrat(5) -> Konstanta.kuadrat(5) [static]")
println("// Pemetaan.balikString(s) -> Pemetaan.balikString(s) [static]")
println("// Konstanta.PI -> Konstanta.PI [static field]")
println("// VersiApp.VERSI -> VersiApp.Companion.getVERSI()")
}Output yang Dihasilkan:
=== Companion tanpa @JvmStatic ===
VersiApp.VERSI : 1.0.0
VersiApp.namaLengkap() : BelajarKotlin 1.0.0
=== Companion dengan @JvmStatic/@JvmField ===
Konstanta.PI : 3.14159
Konstanta.MAKSIMAL : 100
Konstanta.namaKelas() : Konstanta
Konstanta.kuadrat(5) : 25
=== Object singleton ===
Pemetaan.balikString("Halo") : olaH
Pemetaan.DEFAULT_PREFIX : log_
=== Factory method ===
Produk.kosong() : Produk(id=0, nama=)
Produk.dariBaris(...) : Produk(id=42, nama=Buku Kotlin)
=== Perbandingan signature ===
// VersiApp.namaLengkap() -> VersiApp.Companion.namaLengkap()
// Konstanta.kuadrat(5) -> Konstanta.kuadrat(5) [static]
// Pemetaan.balikString(s) -> Pemetaan.balikString(s) [static]
// Konstanta.PI -> Konstanta.PI [static field]
// VersiApp.VERSI -> VersiApp.Companion.getVERSI()Mari kita telaah baris-baris kode di atas:
- Baris 9-13 (
companion objecttanpa@JvmStatic): di bytecode Java,VersiApppunya inner classCompaniondengan instanceINSTANCE. MethodnamaLengkap()dipanggil sebagaiVersiApp.Companion.INSTANCE.namaLengkap(). PropertiVERSIdiakses viaVersiApp.Companion.INSTANCE.getVERSI(). Sintaks Java yang dihasilkan berantakan. - Baris 16-29 (
companion objectdengan@JvmStaticdan@JvmField):@JvmStaticpada methodnamaKelas()dankuadrat(x)menghasilkan static method sejati di bytecode Java.@JvmFieldpada propertiPIdanMAKSIMALmenghasilkan fieldpublic static finallangsung tanpa getter. Sisi Java memanggilKonstanta.kuadrat(5)dan membacaKonstanta.PIsecara langsung. - Baris 32-37 (
object Pemetaan):objectdi Kotlin menjadi singleton class dengan instancePemetaan.INSTANCE. Tanpa@JvmStatic, Java harus menulisPemetaan.INSTANCE.balikString("Halo"). Dengan@JvmStatic, kompilator menghasilkan static method tambahanPemetaan.balikString(String)yang mendelegasikan keINSTANCE.@JvmField val DEFAULT_PREFIXmenjadi field public static final. - Baris 40-52 (
data class Produkdengan companion factory): pola factory method dicompanion objectsangat umum. Tanpa@JvmStatic, Java harus menulisProduk.Companion.INSTANCE.kosong()— sangat berantakan. Dengan@JvmStatic, Java memanggilProduk.kosong()danProduk.dariBaris("42,Buku Kotlin")secara langsung, sama seperti idiom Java native. - Baris 67-71 (perbandingan signature): tabel ringkasan ini menunjukkan translasi yang terjadi di bytecode. Perbedaan paling mencolok adalah pada
VersiApp(memerlukanCompanion.perantara) versusKonstanta(static sejati).
Pemilihan Anotasi: Kapan Pakai Apa #
| Skenario | Anotasi | Alasan |
|---|---|---|
| Library Kotlin murni untuk Kotlin | tidak perlu anotasi | Sintaks Kotlin sudah idiomatik |
| Library Kotlin dipanggil Java | @JvmStatic + @JvmField | Java tidak perlu Companion. perantara |
| Konstanta string/primitif publik | const val | Field public static final langsung, tanpa getter |
| Singleton dengan state | object saja (cukup) | Java pakai INSTANCE eksplisit |
| Method factory di data class | @JvmStatic | Java panggil Class.factory() langsung |
Referensi Lanjutan #
- Static fields and methods
— panduan resmi Kotlin untuk static fields dan
@JvmStatic - Calling Java from Kotlin — dokumentasi Java interop Kotlin terkait anggota statis
Selanjutnya #
Bab berikutnya membedah @JvmField dan @JvmStatic secara mendalam — perbedaan keduanya, kapan harus memilih satu dibanding yang lain, dan contoh kasus nyata di library open source.