Kotlin memungkinkan default argumen pada parameter function: fun f(a: Int, b: Int = 0). Pemanggil Kotlin dapat memanggil f(5) atau f(5, 10) — kompilator menyisipkan nilai default. Java tidak memiliki fitur ini, sehingga method Kotlin dengan default argumen hanya menghasilkan satu signature Java dengan semua parameter wajib. Anotasi @JvmOverloads mengatasi keterbatasan ini dengan menghasilkan beberapa overload Java secara otomatis. Bab ini membahas cara kerja, jumlah overload yang dihasilkan, dan strategi pemakaiannya.

Aturan Jumlah Overload #

Untuk function dengan n parameter default, @JvmOverloads menghasilkan n + 1 overload. Aturan ini berlaku untuk function top-level, constructor, dan method instance.

Function KotlinJumlah Overload Java
fun f(a, b = 0, c = 1) (2 default)3
fun f(a, b = 0, c = 1, d = 2) (3 default)4
fun f(a) (0 default)1 (sama seperti tanpa anotasi)
fun f(a = 0, b = 0) (semua default)3

Overload yang dihasilkan mengikuti urutan parameter: parameter terkanan yang default-nya disisipkan terlebih dahulu. Untuk fun f(a, b = 0, c = 1), overload yang dihasilkan adalah f(a), f(a, b), f(a, b, c). Kita tidak bisa membuat f(a, c) (skip b) — Java tidak mendukung named argumen.

Contoh Lengkap: Top-Level, Constructor, Method Instance #

Kode berikut mendemonstrasikan @JvmOverloads pada tiga konteks: top-level function, constructor, dan method instance:

kotlin
// Demonstrasi @JvmOverloads: menghasilkan beberapa overload Java untuk default argumen Kotlin
//
// Kotlin: fun f(a: Int, b: Int = 0, c: Int = 1)
// Java tanpa @JvmOverloads: hanya f(int,int,int) tersedia
// Java dengan @JvmOverloads: f(int), f(int,int), f(int,int,int) tersedia

// 1. TANPA @JvmOverloads: Java harus memberi semua argumen
fun hitungTotalTanpaAnotasi(harga: Int, persen: Int = 10, ongkir: Int = 5000): Int {
    return harga + (harga * persen) / 100 + ongkir
}

// 2. DENGAN @JvmOverloads: Java dapat memanggil 3 variasi
@JvmOverloads
fun hitungTotalDenganAnotasi(harga: Int, persen: Int = 10, ongkir: Int = 5000): Int {
    return harga + (harga * persen) / 100 + ongkir
}

// 3. @JvmOverloads pada constructor
data class Produk @JvmOverloads constructor(
    val nama: String,
    val harga: Int = 0,
    val kategori: String = "umum",
    val stok: Int = 0,
)

// 4. @JvmOverloads pada method instance
class Keranjang(val items: MutableList<String> = mutableListOf()) {

    @JvmOverloads
    fun tambah(nama: String, jumlah: Int = 1, catatan: String = ""): Boolean {
        repeat(jumlah) { items.add("$nama#$catatan") }
        return true
    }
}

// 5. Aturan jumlah overload yang dihasilkan:
//    bila ada n parameter default, akan ada n+1 overload.
//    Misal: 3 parameter default -> 4 signature.
fun main() {
    println("=== Tanpa @JvmOverloads (Kotlin panggil bisa pakai default) ===")
    println("hitungTotalTanpaAnotasi(100000)          : ${hitungTotalTanpaAnotasi(100000)}")
    println("hitungTotalTanpaAnotasi(100000, 20)      : ${hitungTotalTanpaAnotasi(100000, 20)}")
    println("hitungTotalTanpaAnotasi(100000, 20, 0)   : ${hitungTotalTanpaAnotasi(100000, 20, 0)}")
    // Di Java: HANYA hitungTotalTanpaAnotasi(100000, 10, 5000) yang tersedia

    println()
    println("=== Dengan @JvmOverloads ===")
    println("hitungTotalDenganAnotasi(100000)          : ${hitungTotalDenganAnotasi(100000)}")
    println("hitungTotalDenganAnotasi(100000, 20)      : ${hitungTotalDenganAnotasi(100000, 20)}")
    println("hitungTotalDenganAnotasi(100000, 20, 0)   : ${hitungTotalDenganAnotasi(100000, 20, 0)}")
    // Di Java: ketiga signature di atas tersedia

    println()
    println("=== @JvmOverloads pada constructor ===")
    val p1 = Produk("Buku Kotlin")
    val p2 = Produk("Laptop", 15000000)
    val p3 = Produk("Mouse", 250000, "elektronik")
    val p4 = Produk("Keyboard", 750000, "elektronik", 10)
    println("p1: $p1")
    println("p2: $p2")
    println("p3: $p3")
    println("p4: $p4")
    // Di Java: 4 constructor tersedia (4,3,2,1 argumen)

    println()
    println("=== @JvmOverloads pada method instance ===")
    val k = Keranjang()
    k.tambah("Pensil")
    k.tambah("Buku", 3)
    k.tambah("Pulpen", 2, "warna biru")
    println("items: ${k.items}")
    // Di Java: keranjang.tambah("Pensil"), .tambah("Buku", 3), .tambah("Pulpen", 2, "warna biru")

    println()
    println("=== Jumlah overload yang dihasilkan ===")
    println("// Produk(nama, harga=0, kategori=\"umum\", stok=0)")
    println("//   -> 4 signature: (String), (String,int), (String,int,String), (String,int,String,int)")
    println("// hitungTotalDenganAnotasi(harga, persen=10, ongkir=5000)")
    println("//   -> 3 signature: (int), (int,int), (int,int,int)")
}

Output yang Dihasilkan:

text
=== Tanpa @JvmOverloads (Kotlin panggil bisa pakai default) ===
hitungTotalTanpaAnotasi(100000)          : 115000
hitungTotalTanpaAnotasi(100000, 20)      : 125000
hitungTotalTanpaAnotasi(100000, 20, 0)   : 120000

=== Dengan @JvmOverloads ===
hitungTotalDenganAnotasi(100000)          : 115000
hitungTotalDenganAnotasi(100000, 20)      : 125000
hitungTotalDenganAnotasi(100000, 20, 0)   : 120000

=== @JvmOverloads pada constructor ===
p1: Produk(nama=Buku Kotlin, harga=0, kategori=umum, stok=0)
p2: Produk(nama=Laptop, harga=15000000, kategori=umum, stok=0)
p3: Produk(nama=Mouse, harga=250000, kategori=elektronik, stok=0)
p4: Produk(nama=Keyboard, harga=750000, kategori=elektronik, stok=10)

=== @JvmOverloads pada method instance ===
items: [Pensil#, Buku#, Buku#, Buku#, Pulpen#warna biru, Pulpen#warna biru]

=== Jumlah overload yang dihasilkan ===
// Produk(nama, harga=0, kategori="umum", stok=0)
//   -> 4 signature: (String), (String,int), (String,int,String), (String,int,String,int)
// hitungTotalDenganAnotasi(harga, persen=10, ongkir=5000)
//   -> 3 signature: (int), (int,int), (int,int,int)

Mari kita telaah baris-baris kode di atas:

  1. Baris 7 (fun hitungTotalTanpaAnotasi(harga, persen=10, ongkir=5000)): tanpa @JvmOverloads, bytecode Java hanya menghasilkan satu signature int hitungTotalTanpaAnotasi(int, int, int). Pemanggil Java harus selalu menyediakan ketiga argumen. Pemanggil Kotlin dapat memakai default argumen karena kompilator Kotlin menyisipkan nilai default di call site.
  2. Baris 13 (@JvmOverloads fun hitungTotalDenganAnotasi(...)): anotasi menghasilkan tiga overload Java — hitungTotalDenganAnotasi(int), hitungTotalDenganAnotasi(int, int), hitungTotalDenganAnotasi(int, int, int). Setiap overload memanggil overload lengkap dengan nilai default untuk parameter yang tidak disebut.
  3. Baris 19-24 (data class Produk @JvmOverloads constructor(...)): @JvmOverloads pada constructor menghasilkan empat constructor Java. Setiap constructor memanggil constructor lengkap dengan nilai default. Data class tetap menghasilkan equals, hashCode, toString, dan copy seperti biasa.
  4. Baris 27-33 (Keranjang.tambah(...) dengan @JvmOverloads): method instance juga mendukung anotasi ini. Tiga overload Java dihasilkan: tambah(String), tambah(String, int), tambah(String, int, String). Setiap overload mendelegasikan ke overload lengkap.
  5. Baris 41-43 (pemanggilan tanpa anotasi): meski tanpa @JvmOverloads, pemanggil Kotlin tetap dapat memakai default argumen — ini fitur level bahasa Kotlin. Anotasi hanya berdampak pada visibilitas Java.
  6. Baris 58-62 (pemanggilan dengan berbagai argumen): Produk("Buku Kotlin") memakai 3 default argumen, Produk("Laptop", 15000000) memakai 2 default, dan seterusnya. Semua valid di sisi Kotlin; @JvmOverloads memastikan semuanya juga valid di sisi Java.

Kapan Memakai @JvmOverloads #

SkenarioPakai @JvmOverloads?Alasan
Library Kotlin dipanggil Java, banyak default argumenYaJava mendapat API idiomatik
Library Kotlin murni untuk KotlinTidak perluDefault argumen sudah cukup
Constructor dengan 5+ default argumenPertimbangkan5+ overload dapat memperbesar bytecode
Method yang jarang dipanggil JavaTidak perluTidak ada manfaat, hanya menambah bytecode

Referensi Lanjutan #

Selanjutnya #

Selain @JvmOverloads, ada anotasi @JvmName yang dapat mengubah nama method atau file class yang dihasilkan. Bab berikutnya membahas penggunaannya untuk disambiguasi, penamaan idiomatik, dan kasus khusus seperti type erasure pada List<String> vs List<Int>.