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 Kotlin | Padanan Java |
|---|---|
val nama: String | getNama(): String |
var email: String | getEmail(): String + setEmail(String) |
val aktif: Boolean | isAktif(): Boolean (konvensi isXxx untuk Boolean) |
var aktif: Boolean | isAktif() + setAktif(boolean) |
Dan sebaliknya, dari sisi Kotlin yang memakai kode Java:
| Konstruk Java | Akses 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() saja | properti 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:
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:
=== 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:
- Baris 12 (
val nama: String): propertivalmenghasilkan gettergetNama()di bytecode Java. Kode Java memanggilpengguna.getNama(). Sisi Kotlin memakai sintaks propertipengguna.nama. - Baris 13 (
var email: String): propertivarmenghasilkan dua methodgetEmail()dansetEmail(String)di bytecode Java. Sisi Kotlin memakaipengguna.emailuntuk membaca danpengguna.email = "..."untuk menulis. - Baris 14 (
val aktif: Boolean): propertiBooleanbernamaaktifmengikuti konvensi JavaisXxxuntuk getter, sehingga menghasilkanisAktif()(bukangetAktif()). Sisi Kotlin tetap menulispengguna.aktif, namun bytecode Java melihatisAktif(). - Baris 15 (
var umur: Int): propertivarbertipeInt(non-Boolean) mengikuti pola standargetUmur()/setUmur(int). Kotlin mengelola autoboxing antaraIntdanintJava secara transparan. - Baris 23-26 (
var saldo: Intdengan custom setter): blokset(value) { ... field = value }memungkinkan validasi sebelum penugasan. Identifierfieldadalah backing field khusus yang hanya dapat diakses dari dalam getter/setter. Tanpafield, setter akan menyebabkan rekursi tak terbatas (memanggil setter-nya sendiri). - Baris 36-39 (
tanggal.year,tanggal.monthValue): kelas JavaLocalDatepunya methodgetYear(),getMonthValue(),getDayOfMonth(). Kotlin menyajikannya sebagai propertiyear,monthValue,dayOfMonth. Method BooleanisLeapYear()menjadi propertiisLeapYear(tetap pakaiiskarena konvensi Java). - Baris 51 (
rekening.saldo = 50000): penugasan memanggil custom setter, yang memvalidasivalue >= 0sebelum menulis kefield. Bila validasi gagal,requiremelemparIllegalArgumentExceptiondanfieldtidak 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.
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 #
- Calling Java from Kotlin
— dokumentasi Java interop Kotlin untuk konvensi getter/setter
getK/setK
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.