Operator

Operator #

Operator adalah simbol atau kata kunci yang memerintahkan compiler untuk melakukan operasi tertentu pada satu atau lebih nilai. Di Kotlin, operator bukan sekadar sintaks — sebagian besar dari mereka adalah fungsi yang bisa di-overload pada kelas kustom. Artinya, ketika kamu menulis a + b, Kotlin sebenarnya memanggil a.plus(b). Ini membuka kemungkinan untuk mendefinisikan ulang perilaku operator pada tipe buatanmu sendiri. Artikel ini membahas semua kategori operator di Kotlin secara mendalam: dari yang paling dasar seperti aritmatika dan perbandingan, hingga yang khas Kotlin seperti operator null-safety, operator range, dan operator infiks.

Operator Aritmatika #

Operator aritmatika melakukan operasi matematika dasar. Semua operator ini bisa di-overload untuk kelas kustom.

val a = 17
val b = 5

println(a + b)   // 22 — penjumlahan
println(a - b)   // 12 — pengurangan
println(a * b)   // 85 — perkalian
println(a / b)   // 3  — pembagian integer (bukan 3.4!)
println(a % b)   // 2  — sisa pembagian (modulus)

Pembagian Integer vs Pembagian Desimal #

Ini adalah sumber bug yang sangat umum untuk pemula. Pembagian antara dua Int selalu menghasilkan Int — bagian desimalnya dipotong, bukan dibulatkan.

// ANTI-PATTERN: pembagian integer yang tidak disengaja
val rata = 7 / 2          // 3 — bukan 3.5!
val persentase = 1 / 3    // 0 — bukan 0.333!

// BENAR: konversi salah satu operand ke Double terlebih dahulu
val rata = 7.0 / 2        // 3.5
val rata2 = 7 / 2.0       // 3.5
val rata3 = 7.toDouble() / 2  // 3.5
val persentase = 1.0 / 3      // 0.3333...

Operator Unary #

Operator unary bekerja pada satu operand:

val positif = 10
val negatif = -positif   // -10 — unary minus
val jugaPositif = +negatif   // -10 (unary plus tidak mengubah nilai)

Operator Penugasan #

Operator penugasan menyimpan nilai ke variabel. Semua bentuk compound assignment (+=, -=, dll.) hanya bekerja dengan var.

var skor = 0

skor = 100       // penugasan biasa
skor += 50       // skor = skor + 50 → 150
skor -= 30       // skor = skor - 30 → 120
skor *= 2        // skor = skor * 2  → 240
skor /= 4        // skor = skor / 4  → 60
skor %= 7        // skor = skor % 7  → 4

Tabel referensi cepat:

OperatorSetara denganContoh
=Penugasan langsungx = 5
+=x = x + nilaix += 3
-=x = x - nilaix -= 3
*=x = x * nilaix *= 3
/=x = x / nilaix /= 3
%=x = x % nilaix %= 3

Operator Inkrement dan Dekrement #

++ dan -- menambah atau mengurangi nilai sebesar 1. Keduanya hadir dalam dua bentuk: prefix (sebelum variabel) dan postfix (setelah variabel).

var n = 5

// Postfix: ekspresi menggunakan nilai SEBELUM perubahan
println(n++)   // cetak 5, lalu n menjadi 6
println(n--)   // cetak 6, lalu n menjadi 5
println(n)     // 5

// Prefix: ekspresi menggunakan nilai SESUDAH perubahan
println(++n)   // n menjadi 6, lalu cetak 6
println(--n)   // n menjadi 5, lalu cetak 5
println(n)     // 5

Perbedaan prefix dan postfix paling terasa ketika digunakan di dalam ekspresi:

var i = 3

// ANTI-PATTERN: campur increment dan ekspresi dalam satu baris — susah dibaca
val hasil = i++ * 2   // hasil = 6, i menjadi 4 — tidak intuitif

// BENAR: pisahkan increment dari ekspresi
i++
val hasil = i * 2     // lebih jelas

Operator Perbandingan #

Operator perbandingan membandingkan dua nilai dan mengembalikan Boolean. Di Kotlin, == memanggil fungsi equals() — bukan membandingkan referensi memori seperti di Java.

val x = 5
val y = 3

println(x == y)   // false — sama dengan (structural equality)
println(x != y)   // true  — tidak sama dengan
println(x > y)    // true  — lebih besar dari
println(x < y)    // false — lebih kecil dari
println(x >= y)   // true  — lebih besar atau sama dengan
println(x <= y)   // false — lebih kecil atau sama dengan

== vs === — Structural vs Referential Equality #

Ini adalah perbedaan penting yang sering membingungkan developer yang datang dari Java:

val a = "Kotlin"
val b = "Kotlin"
val c = a

// == membandingkan nilai (structural equality) — memanggil equals()
println(a == b)    // true — nilai sama
println(a == c)    // true — nilai sama

// === membandingkan referensi (referential equality) — apakah objek yang persis sama?
println(a === b)   // true/false — tergantung string interning JVM
println(a === c)   // true — c adalah referensi yang sama dengan a

// Contoh lebih jelas dengan data class
data class Titik(val x: Int, val y: Int)

val p1 = Titik(3, 7)
val p2 = Titik(3, 7)
val p3 = p1

println(p1 == p2)    // true  — nilai x dan y sama
println(p1 === p2)   // false — dua objek berbeda di memori
println(p1 === p3)   // true  — referensi yang sama persis

Operator Comparator compareTo #

Operator <, >, <=, >= sebenarnya memanggil compareTo() di balik layar. Untuk tipe kustom yang mengimplementasikan Comparable, operator ini bekerja secara otomatis:

data class Versi(val major: Int, val minor: Int, val patch: Int) : Comparable<Versi> {
    override fun compareTo(other: Versi): Int {
        return compareValuesBy(this, other, { it.major }, { it.minor }, { it.patch })
    }
}

val v1 = Versi(1, 2, 0)
val v2 = Versi(1, 3, 0)

println(v1 < v2)    // true
println(v1 > v2)    // false
println(v2 >= v1)   // true

val versi = listOf(Versi(2, 0, 0), Versi(1, 9, 5), Versi(1, 10, 0))
println(versi.sorted())   // terurut dari kecil ke besar

Operator Logika #

Operator logika menggabungkan ekspresi Boolean. Kotlin mendukung short-circuit evaluation — evaluasi berhenti segera setelah hasilnya sudah pasti.

val a = true
val b = false

println(a && b)   // false — AND: keduanya harus true
println(a || b)   // true  — OR: salah satu cukup true
println(!a)       // false — NOT: negasi

Short-Circuit Evaluation #

Karena Kotlin mengevaluasi operator logika secara short-circuit, urutan ekspresi memengaruhi performa dan keamanan:

// && berhenti di operand pertama jika hasilnya false
// Manfaatkan ini untuk cek keamanan sebelum operasi berisiko
val daftar: List<String>? = ambilDaftar()

// ANTI-PATTERN: akses langsung tanpa pengecekan urutan
if (daftar != null && daftar.isNotEmpty() && daftar[0].length > 5) {
    // aman karena short-circuit: jika daftar null, ekspresi berikutnya tidak dievaluasi
}

// || berhenti di operand pertama jika hasilnya true
// Taruh kondisi yang paling sering true di kiri untuk efisiensi
val cacheHit = ambilDariCache() || ambilDariDatabase()
// jika ambilDariCache() true, ambilDariDatabase() tidak dipanggil sama sekali

Operator Logika pada Bit — and, or, xor sebagai Infix #

Berbeda dari && dan || yang bekerja pada Boolean, Kotlin menyediakan versi non-short-circuit sebagai fungsi infix:

// Evaluasi keduanya tanpa short-circuit (jarang dibutuhkan)
val keduanya = true.and(false)   // false
val salahSatu = false.or(true)   // true
val berbeda = true.xor(true)     // false

Operator Null-Safety #

Kotlin memiliki serangkaian operator khusus untuk bekerja dengan nilai nullable — ini adalah fitur yang tidak ada di Java dan membedakan Kotlin secara signifikan.

Safe Call Operator ?. #

Mengakses member hanya jika objek tidak null. Mengembalikan null jika objek null.

var nama: String? = ambilNama()

// ANTI-PATTERN: cek manual berulang
if (nama != null) {
    if (nama.isNotEmpty()) {
        println(nama.uppercase())
    }
}

// BENAR: chaining safe call
println(nama?.takeIf { it.isNotEmpty() }?.uppercase())

// Chaining panjang — setiap langkah aman
val panjangNamaDepan = pengguna?.profil?.namaDepan?.trim()?.length

Elvis Operator ?: #

Memberikan nilai default ketika ekspresi kiri bernilai null:

val nama: String? = ambilNama()

val tampilan = nama ?: "Anonim"          // "Anonim" jika nama null
val panjang = nama?.length ?: 0          // 0 jika nama null
val valid = nama?.isNotBlank() ?: false  // false jika nama null

// Elvis dengan throw — validasi ringkas
val alamatPasti = alamat ?: throw IllegalStateException("Alamat wajib diisi")

// Elvis dalam chain panjang
val kota = pengguna?.alamat?.kota ?: "Kota tidak diketahui"

Non-Null Assertion !! #

Memberitahu compiler bahwa kamu yakin nilai tidak null. Melempar NullPointerException jika ternyata null.

var nilai: String? = "Pasti ada"

// Gunakan hanya jika benar-benar yakin
val panjang = nilai!!.length  // 8

// ANTI-PATTERN: !! sembarangan — lebih berbahaya dari Java NPE karena kamu yang memintanya
fun prosesInput(input: String?) {
    val hasil = input!!.trim()  // ✗ crash jika input null
}

// BENAR: tangani null dengan elegan
fun prosesInput(input: String?) {
    val hasil = input?.trim() ?: return  // keluar dari fungsi jika null
    // lanjut proses hasil yang pasti non-null
}
Setiap !! dalam kode adalah pengakuan bahwa kamu mengambil alih tanggung jawab null-safety dari compiler. Jika yakin suatu nilai tidak null karena logika program, lebih baik buktikan itu ke compiler dengan smart cast atau restrukturisasi kode — bukan dengan !!.

Operator Range #

Range adalah salah satu fitur Kotlin yang sangat ekspresif untuk mendefinisikan rentang nilai secara deklaratif.

// Inklusif di kedua ujung: 1, 2, 3, 4, 5
val rangeInklusif = 1..5
println(3 in rangeInklusif)   // true
println(6 in rangeInklusif)   // false

// Eksklusif di ujung kanan: 0, 1, 2, 3, 4
val rangeEksklusif = 0 until 5
println(5 in rangeEksklusif)  // false
println(4 in rangeEksklusif)  // true

// Mundur: 5, 4, 3, 2, 1
val rangeMundur = 5 downTo 1

// Dengan langkah
val rangeGanjil = 1..10 step 2      // 1, 3, 5, 7, 9
val rangeTurun = 10 downTo 0 step 3 // 10, 7, 4, 1

// Range karakter
val rangeHuruf = 'a'..'z'
println('m' in rangeHuruf)   // true
println('A' in rangeHuruf)   // false (case-sensitive)

Range dalam Loop #

Range paling sering digunakan bersama for:

for (i in 1..5) print("$i ")           // 1 2 3 4 5
for (i in 0 until 5) print("$i ")      // 0 1 2 3 4
for (i in 5 downTo 1) print("$i ")     // 5 4 3 2 1
for (i in 0..20 step 5) print("$i ")   // 0 5 10 15 20

Range dalam Kondisi #

Range sangat berguna sebagai kondisi when atau if:

val nilai = 82

// if dengan range
val lulus = nilai in 60..100

// when dengan range — lebih ekspresif dari if-else chain panjang
val grade = when (nilai) {
    in 90..100 -> "A"
    in 80..89  -> "B"
    in 70..79  -> "C"
    in 60..69  -> "D"
    else       -> "E"
}
println(grade)  // B

Operator in dan !in #

Operator in memeriksa keanggotaan dalam koleksi, range, atau tipe apapun yang mengimplementasikan contains().

val buah = listOf("mangga", "apel", "jeruk")

println("apel" in buah)    // true
println("durian" in buah)  // false
println("durian" !in buah) // true

// in pada String — cek substring
println("otlin" in "Kotlin")   // true
println("java" in "Kotlin")    // false

// in pada Map — cek key
val kamus = mapOf("id" to "Indonesia", "en" to "English")
println("id" in kamus)   // true
println("fr" in kamus)   // false

Operator is dan !is — Type Check #

is memeriksa apakah sebuah objek adalah instance dari tipe tertentu. Setelah is berhasil, Kotlin secara otomatis melakukan smart cast.

fun deskripsikan(nilai: Any): String {
    return when (nilai) {
        is String  -> "String '${nilai.uppercase()}'"  // nilai sudah di-cast ke String
        is Int     -> "Int: ${nilai * 2}"              // nilai sudah di-cast ke Int
        is Double  -> "Double: ${"%.2f".format(nilai)}"
        is Boolean -> if (nilai) "Benar" else "Salah"
        is List<*> -> "List dengan ${nilai.size} elemen"
        else       -> "Tipe tidak dikenal"
    }
}

println(deskripsikan("halo"))          // String 'HALO'
println(deskripsikan(42))              // Int: 84
println(deskripsikan(3.14))            // Double: 3.14
println(deskripsikan(listOf(1, 2, 3))) // List dengan 3 elemen
// !is — kebalikan is
val teks: Any = "Kotlin"
if (teks !is Int) {
    println("Bukan integer")
}

Operator Bitwise #

Operator bitwise bekerja pada representasi biner dari bilangan bulat. Di Kotlin, operator ini ditulis sebagai fungsi infix — bukan simbol seperti &, |, ^ di Java.

Fungsi InfixJavaOperasi
and&AND bitwise
or|OR bitwise
xor^XOR bitwise
inv()~Inversi / NOT bitwise
shl(n)<<Geser kiri n bit
shr(n)>>Geser kanan n bit (signed)
ushr(n)>>>Geser kanan n bit (unsigned)
val a = 0b1010  // 10 dalam desimal
val b = 0b1100  // 12 dalam desimal

println(a and b)    // 0b1000 = 8   — AND: 1 jika keduanya 1
println(a or b)     // 0b1110 = 14  — OR: 1 jika salah satu 1
println(a xor b)    // 0b0110 = 6   — XOR: 1 jika berbeda
println(a.inv())    // -11           — inversi semua bit

println(1 shl 3)    // 8  — geser kiri 3 bit = kalikan 2³
println(16 shr 2)   // 4  — geser kanan 2 bit = bagi 2²
println(-1 ushr 1)  // 2147483647 — geser kanan tanpa sign extension

Penggunaan Praktis Bitwise #

Bitwise sering digunakan untuk flag dan mask:

// Definisikan flag sebagai bit
const val IZIN_BACA   = 0b001  // 1
const val IZIN_TULIS  = 0b010  // 2
const val IZIN_HAPUS  = 0b100  // 4

// Gabungkan flag dengan OR
val izinAdmin = IZIN_BACA or IZIN_TULIS or IZIN_HAPUS  // 7
val izinUser  = IZIN_BACA or IZIN_TULIS                 // 3

// Periksa flag dengan AND
fun bisaBaca(izin: Int)  = (izin and IZIN_BACA)  != 0
fun bisaTulis(izin: Int) = (izin and IZIN_TULIS) != 0
fun bisaHapus(izin: Int) = (izin and IZIN_HAPUS) != 0

println(bisaBaca(izinUser))   // true
println(bisaHapus(izinUser))  // false
println(bisaHapus(izinAdmin)) // true

Operator Infiks #

Kotlin mengizinkan definisi fungsi yang dipanggil dengan notasi infix — tanpa tanda titik dan kurung. Ini membuat kode terasa lebih natural dan mudah dibaca.

// to — membuat Pair, digunakan di mapOf
val pasangan = "kunci" to "nilai"   // Pair<String, String>
val peta = mapOf("satu" to 1, "dua" to 2)

// until — membuat range eksklusif
val rentang = 0 until 10

// step — langkah dalam range
val selang = 1..10 step 2

// downTo — range menurun
val turun = 10 downTo 1

// and, or, xor — operasi bitwise
val hasil = 5 and 3

Kamu juga bisa mendefinisikan fungsi infix sendiri dengan keyword infix:

infix fun Int.kelipatan(n: Int): Boolean = this % n == 0

println(12 kelipatan 4)   // true
println(7 kelipatan 3)    // false

// Contoh lain — DSL-style
infix fun String.bersamaDengan(lain: String) = "$this dan $lain"

val kalimat = "Kotlin" bersamaDengan "Java"
println(kalimat)  // Kotlin dan Java

Operator Overloading #

Di Kotlin, sebagian besar operator adalah fungsi yang bisa di-overload pada kelas kustom dengan menandai fungsi menggunakan keyword operator.

data class Vektor(val x: Double, val y: Double) {

    // Overload operator +
    operator fun plus(lain: Vektor) = Vektor(x + lain.x, y + lain.y)

    // Overload operator -
    operator fun minus(lain: Vektor) = Vektor(x - lain.x, y - lain.y)

    // Overload operator * (perkalian dengan skalar)
    operator fun times(skalar: Double) = Vektor(x * skalar, y * skalar)

    // Overload operator unary minus
    operator fun unaryMinus() = Vektor(-x, -y)

    // Overload operator == (lewat equals)
    override fun equals(other: Any?): Boolean {
        if (other !is Vektor) return false
        return x == other.x && y == other.y
    }

    val panjang get() = Math.sqrt(x * x + y * y)

    override fun toString() = "($x, $y)"
}

val v1 = Vektor(3.0, 4.0)
val v2 = Vektor(1.0, 2.0)

println(v1 + v2)      // (4.0, 6.0)
println(v1 - v2)      // (2.0, 2.0)
println(v1 * 2.0)     // (6.0, 8.0)
println(-v1)          // (-3.0, -4.0)
println(v1 == v2)     // false
println(v1.panjang)   // 5.0

Prioritas Operator #

Saat banyak operator digunakan dalam satu ekspresi, urutan evaluasinya ditentukan oleh prioritas. Operator dengan prioritas lebih tinggi dievaluasi lebih dulu.

flowchart TD
    A["Prioritas Tertinggi"] --> B["Postfix: ++, --"]
    B --> C["Prefix: --, ++, -, +, !"]
    C --> D["Perkalian: *, /, %"]
    D --> E["Penjumlahan: +, -"]
    E --> F["Range: .., until"]
    F --> G["Infix: shl, shr, ushr, and, or, xor"]
    G --> H["Elvis: ?:"]
    H --> I["Perbandingan: <, >, <=, >=, in, !in, is, !is"]
    I --> J["Kesetaraan: ==, !="]
    J --> K["Konjungsi: &&"]
    K --> L["Disjungsi: ||"]
    L --> M["Spread: *"]
    M --> N["Prioritas Terendah: =, +=, -=, *=, /=, %="]
// Contoh prioritas dalam praktik
val hasil = 2 + 3 * 4       // 14 — perkalian lebih dulu
val jelas = 2 + (3 * 4)     // 14 — sama, tapi lebih eksplisit

val logika = true || false && false  // true — && lebih tinggi dari ||
// setara dengan: true || (false && false) = true || false = true

// Gunakan kurung untuk kejelasan, bukan menghapal prioritas
val lebihJelas = true || (false && false)  // niat yang sama, lebih mudah dibaca
Menghapal seluruh tabel prioritas operator bukan cara yang baik. Lebih baik gunakan tanda kurung secara eksplisit ketika ekspresi melibatkan lebih dari dua operator berbeda kategori. Kode yang jelas lebih berharga dari kode yang ringkas tapi ambigu.

Ringkasan #

  • Pembagian integer7 / 2 = 3, bukan 3.5. Konversi salah satu operand ke Double jika membutuhkan hasil desimal.
  • == vs ===== membandingkan nilai (memanggil equals()); === membandingkan referensi memori. Hampir selalu gunakan == untuk perbandingan nilai.
  • Short-circuit evaluation&& berhenti jika operand kiri false; || berhenti jika operand kiri true. Manfaatkan ini untuk cek keamanan: taruh kondisi null-check di kiri.
  • Operator null-safety?. untuk safe call, ?: untuk nilai default (Elvis), !! untuk assert non-null (gunakan dengan hati-hati). Ini adalah trio yang menggantikan null-check manual yang verbose.
  • Range1..5 (inklusif), 0 until 5 (eksklusif kanan), 5 downTo 1 (mundur), 1..10 step 2 (dengan langkah). Gunakan dalam for, when, dan kondisi in.
  • is dan smart cast — setelah nilai is String, compiler otomatis memperlakukan nilai sebagai String tanpa cast eksplisit.
  • Bitwise menggunakan infix — di Kotlin, and, or, xor, shl, shr adalah fungsi infix, bukan simbol &, |, ^ seperti di Java.
  • Operator overloading — operator di Kotlin adalah fungsi bernama (plus, minus, times, dll). Tandai dengan operator untuk mengizinkan penggunaan simbol operator pada kelas kustom.
  • Operator infiks kustom — tandai fungsi dengan infix untuk memanggilnya tanpa titik dan kurung, berguna untuk membuat DSL yang mudah dibaca.
  • Gunakan kurung untuk kejelasan — jangan mengandalkan hafalan prioritas operator saat ekspresi kompleks. Kurung eksplisit lebih jelas dari ekspresi yang ambigu.

← Sebelumnya: Tipe Data   Berikutnya: Seleksi Kondisi →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact