Java tidak memiliki konsep properti sebagai kelas satu — semua akses field melalui method getXxx() dan setXxx(). Kotlin memperkenalkan properti val/var sebagai warga kelas satu. Kedua dunia harus saling memahami: kode Java memanggil properti Kotlin melalui getter/setter, kode Kotlin mengakses method Java sebagai properti. Bab ini membahas translasi dua arah tersebut beserta konvensi khusus untuk Boolean isXxx().

Aturan Translasi Dua Arah #

Konstruk KotlinPadanan Java
val nama: StringgetNama(): String
var email: StringgetEmail(): String + setEmail(String)
val aktif: BooleanisAktif(): Boolean (konvensi isXxx untuk Boolean)
var aktif: BooleanisAktif() + setAktif(boolean)

Dan sebaliknya, dari sisi Kotlin yang memakai kode Java:

Konstruk JavaAkses Kotlin
T getFoo() / void setFoo(T)properti foo (var)
T getFoo() saja (tanpa set)properti foo (val, read-only)
boolean isFoo() / void setFoo(boolean)properti foo (Boolean, var)
boolean isFoo() sajaproperti foo (Boolean, val)

Aturan ini berlaku otomatis — tidak ada anotasi yang perlu ditambahkan. Kompilator mendeteksi pasangan method dan menyajikan ulang sebagai properti di bytecode maupun di IDE Kotlin.

Contoh Lengkap: Properti Kotlin dan Akses Java #

Kode berikut mendefinisikan kelas Kotlin dengan val, var, dan custom getter/setter, lalu menunjukkan akses properti Kotlin dan properti dari kelas Java LocalDate:

kotlin
import java.time.LocalDate

// Demonstrasi translasi getter/setter Java <-> properti Kotlin:
// - Java getXxx()/setXxx() -> Kotlin properti xxx
// - Kotlin val -> Java getter
// - Kotlin var -> Java getter + setter
// - Kotlin isFoo()/setFoo() -> properti isFoo

// 1. Kelas Kotlin dengan val dan var
class Pengguna(
    val nama: String,           // val -> Java getNama()
    var email: String,          // var -> Java getEmail()/setEmail()
    val aktif: Boolean,          // val Boolean isXxx -> Java isAktif()
    var umur: Int,              // var -> Java getUmur()/setUmur()
)

// 2. Kelas Kotlin dengan custom getter/setter
class RekeningSaldo(saldoAwal: Int) {
    var saldo: Int = saldoAwal
        set(value) {
            require(value >= 0) { "Saldo tidak boleh negatif" }
            field = value
        }

    val isKosong: Boolean
        get() = saldo == 0
}

// 3. Memakai kelas Java LocalDate dengan akses properti Kotlin
fun main() {
    // a. Memakai kelas Kotlin sendiri
    val pengguna = Pengguna(
        nama = "Budi",
        email = "[email protected]",
        aktif = true,
        umur = 25,
    )
    println("=== Properti Kotlin ===")
    println("pengguna.nama   : ${pengguna.nama}")    // panggil getNama()
    println("pengguna.email  : ${pengguna.email}")   // panggil getEmail()
    println("pengguna.aktif  : ${pengguna.aktif}")   // panggil isAktif() (bukan getAktif)
    println("pengguna.umur   : ${pengguna.umur}")    // panggil getUmur()

    pengguna.email = "[email protected]"         // panggil setEmail()
    pengguna.umur = 26                                // panggil setUmur()
    println()
    println("Setelah diubah:")
    println("pengguna.email  : ${pengguna.email}")
    println("pengguna.umur   : ${pengguna.umur}")

    // b. Memakai kelas Java LocalDate: getter menjadi properti
    val tanggal = LocalDate.of(2026, 1, 15)
    println()
    println("=== Getter Java -> Properti Kotlin ===")
    println("tanggal.year        : ${tanggal.year}")         // getYear()
    println("tanggal.monthValue  : ${tanggal.monthValue}")   // getMonthValue()
    println("tanggal.dayOfMonth  : ${tanggal.dayOfMonth}")   // getDayOfMonth()
    println("tanggal.isLeapYear  : ${tanggal.isLeapYear}")   // isLeapYear() (Boolean isXxx)

    // c. Custom getter/setter Kotlin
    val rekening = RekeningSaldo(saldoAwal = 100000)
    println()
    println("=== Custom getter/setter ===")
    println("rekening.saldo    : ${rekening.saldo}")
    println("rekening.isKosong : ${rekening.isKosong}")

    rekening.saldo = 50000                            // setter custom dengan require()
    println("rekening.saldo (setelah) : ${rekening.saldo}")
    println("rekening.isKosong        : ${rekening.isKosong}")

    // d. Setter validation: require() akan melempar exception
    println()
    println("=== Validasi setter ===")
    try {
        rekening.saldo = -1000
    } catch (e: IllegalArgumentException) {
        println("Tangkap exception: ${e.message}")
    }
    println("rekening.saldo akhir : ${rekening.saldo} (tidak berubah)")
}

Output yang Dihasilkan:

text
=== Properti Kotlin ===
pengguna.nama   : Budi
pengguna.email  : [email protected]
pengguna.aktif  : true
pengguna.umur   : 25

Setelah diubah:
pengguna.email  : [email protected]
pengguna.umur   : 26

=== Getter Java -> Properti Kotlin ===
tanggal.year        : 2026
tanggal.monthValue  : 1
tanggal.dayOfMonth  : 15
tanggal.isLeapYear  : false

=== Custom getter/setter ===
rekening.saldo    : 100000
rekening.isKosong : false
rekening.saldo (setelah) : 50000
rekening.isKosong        : false

=== Validasi setter ===
Tangkap exception: Saldo tidak boleh negatif
rekening.saldo akhir : 50000 (tidak berubah)

Mari kita telaah baris-baris kode di atas:

  1. Baris 12 (val nama: String): properti val menghasilkan getter getNama() di bytecode Java. Kode Java memanggil pengguna.getNama(). Sisi Kotlin memakai sintaks properti pengguna.nama.
  2. Baris 13 (var email: String): properti var menghasilkan dua method getEmail() dan setEmail(String) di bytecode Java. Sisi Kotlin memakai pengguna.email untuk membaca dan pengguna.email = "..." untuk menulis.
  3. Baris 14 (val aktif: Boolean): properti Boolean bernama aktif mengikuti konvensi Java isXxx untuk getter, sehingga menghasilkan isAktif() (bukan getAktif()). Sisi Kotlin tetap menulis pengguna.aktif, namun bytecode Java melihat isAktif().
  4. Baris 15 (var umur: Int): properti var bertipe Int (non-Boolean) mengikuti pola standar getUmur()/setUmur(int). Kotlin mengelola autoboxing antara Int dan int Java secara transparan.
  5. Baris 23-26 (var saldo: Int dengan custom setter): blok set(value) { ... field = value } memungkinkan validasi sebelum penugasan. Identifier field adalah backing field khusus yang hanya dapat diakses dari dalam getter/setter. Tanpa field, setter akan menyebabkan rekursi tak terbatas (memanggil setter-nya sendiri).
  6. Baris 36-39 (tanggal.year, tanggal.monthValue): kelas Java LocalDate punya method getYear(), getMonthValue(), getDayOfMonth(). Kotlin menyajikannya sebagai properti year, monthValue, dayOfMonth. Method Boolean isLeapYear() menjadi properti isLeapYear (tetap pakai is karena konvensi Java).
  7. Baris 51 (rekening.saldo = 50000): penugasan memanggil custom setter, yang memvalidasi value >= 0 sebelum menulis ke field. Bila validasi gagal, require melempar IllegalArgumentException dan field tidak diubah — terbukti dari output “saldo akhir : 50000 (tidak berubah)” setelah percobaan -1000.

Backing Field dan Custom Accessor #

Kotlin memungkinkan properti tanpa backing field — getter saja yang mengembalikan nilai hasil komputasi. Contoh: val isKosong: Boolean get() = saldo == 0 tidak memiliki field penyimpan; setiap akses memanggil komputasi saldo == 0. Pendekatan ini idiomatik untuk derived property.

kotlin Pola: derived property tanpa backing field
class Lingkaran(val jariJari: Double) {
    val luas: Double
        get() = Math.PI * jariJari * jariJari

    val keliling: Double
        get() = 2 * Math.PI * jariJari
}

Dari sisi Java, lingkaran.getLuas() dan lingkaran.getKeliling() dipanggil seperti getter biasa. Tidak ada field luas di bytecode — hanya method yang menghitung hasil.

Referensi Lanjutan #

Selanjutnya #

Properti adalah satu perbedaan sintaks. Aspek lain interoperabilitas adalah penanganan checked exception — Java memaksa deklarasi throws, Kotlin tidak. Bab Checked Exceptions Interoperability akan membahas bagaimana Kotlin tetap dapat menangkap checked exception Java tanpa harus mendeklarasikannya.