Precedence dan asosiativitas menentukan urutan evaluasi sebuah ekspresi yang memuat banyak operator. Kotlin mengikuti urutan precedence yang konsisten dan dapat diprediksi, namun tidak sepenuhnya sama dengan Java atau C. Pada chapter ini kita akan menyajikan tabel precedence Kotlin lengkap, menjelaskan asosiativitas left dan right, lalu menunjukkan bagaimana urutan ini memengaruhi hasil perhitungan sehari-hari.

Tabel Precedence Kotlin #

Berikut urutan precedence dari tertinggi (dievaluasi pertama) ke terendah (dievaluasi terakhir):

TingkatKategoriOperatorAsosiativitas
1Postfix++, --, ., ?., ? (index)Left
2Prefix-, +, !, ++, --, labelRight
3Type RHSas, as?, : (type)Left
4Multiplicative*, /, %Left
5Additive+, -Left
6Range..Left
7Infix functionnama function infixLeft
8Elvis?:Right
9Named checksin, !in, is, !isLeft
10Comparison<, >, <=, >=Left
11Equality==, !=Left
12Conjunction&&Left
13Disjunction||Left
14Spread* (di argumen variadic)—
15Assignment=, +=, -=, *=, /=, %=Right

Operator dengan tingkat lebih tinggi dievaluasi lebih dulu. Bila dua operator berada pada tingkat yang sama, asosiativitas menentukan urutannya. Mayoritas operator binary bersifat left-associative, sehingga a - b - c dievaluasi sebagai (a - b) - c. Pengecualian: operator .. (range) dan assignment bersifat right-associative.

Contoh Ekspresi dan Evaluasi #

kotlin
fun main() {
    val a = 10
    val b = 3
    val c = 5

    val hasil1 = a + b * c
    println("a + b * c = $hasil1")

    val hasil2 = (a + b) * c
    println("(a + b) * c = $hasil2")

    val hasil3 = a - b - c
    println("a - b - c = $hasil3")

    val hasil4 = a / b / c
    println("a / b / c = $hasil4")

    val rentang = 1..5
    println("1..5 first=${rentang.first}, last=${rentang.last}")

    val unary = -a + b
    println("-a + b = $unary")

    val elvis1: Int? = null
    val hasil5 = elvis1 ?: a
    println("null ?: a = $hasil5")

    val banding = a > b && b > c || c == 0
    println("(a > b && b > c) || c == 0 = $banding")

    var x = 0
    x += a + b * c
    println("x += a + b * c -> $x")

    val perbandingan = if (a > b) "besar" else "kecil"
    println("a > b ? $perbandingan")

    val stepRange = 1..10 step 2
    println("1..10 step 2 -> ${stepRange.joinToString()}")
}

Output yang Dihasilkan:

text
a + b * c = 25
(a + b) * c = 65
a - b - c = 2
a / b / c = 0
1..5 first=1, last=5
-a + b = -7
null ?: a = 10
(a > b && b > c) || c == 0 = false
x += a + b * c -> 25
a > b ? besar
1..10 step 2 -> 1, 3, 5, 7, 9

Mari kita uraikan setiap ekspresi pada contoh di atas:

  1. a + b * c menghasilkan 25. Operator * (multiplicative) lebih tinggi dari + (additive), sehingga evaluasi menjadi a + (b * c) = 10 + 15 = 25. Tanpa pemahaman precedence, kita mungkin salah mengira hasilnya (10 + 3) * 5 = 65.
  2. (a + b) * c menghasilkan 65 karena tanda kurung memaksa a + b dievaluasi lebih dulu sebelum dikalikan c. Tanda kurung selalu menjadi precedence tertinggi bila ada.
  3. a - b - c menghasilkan 2. Operator - bersifat left-associative, sehingga evaluasi menjadi (a - b) - c = 7 - 5 = 2. Bila bersifat right-associative, hasilnya akan menjadi a - (b - c) = 10 - (-2) = 12, sangat berbeda.
  4. a / b / c menghasilkan 0. Evaluasi: (10 / 3) / 5 = 3 / 5 = 0 (pembagian bulat). Inilah jebakan klasik: bila ingin pembagian float, gunakan a.toDouble() / b / c.
  5. 1..5 membentuk rentang. Operator .. sebenarnya adalah pemanggilan rangeTo() yang dipetakan ke tipe IntRange. Property first dan last membuktikan rentang inklusif di kedua sisi.
  6. -a + b menghasilkan -7. Operator prefix - (unary) lebih tinggi dari + (binary), sehingga evaluasi menjadi (-a) + b = -10 + 3 = -7. Tanpa precedence, -(a + b) akan menghasilkan -13.
  7. elvis1 ?: a menghasilkan 10 karena elvis1 adalah null. Operator elvis ?: mengembalikan operand kanan jika operand kiri null, sebaliknya mengembalikan operand kiri. Operator elvis bersifat right-associative, sehingga a ?: b ?: c dievaluasi sebagai a ?: (b ?: c).
  8. a > b && b > c || c == 0 menghasilkan false. Operator && lebih tinggi dari ||, sehingga evaluasi menjadi (a > b && b > c) || (c == 0) = (true && false) || false = false || false = false. Pemahaman precedence logika ini krusial saat menulis kondisi gabungan.
  9. x += a + b * c menghasilkan 25. Operator assignment += bersifat right-associative dan berada di tingkat paling rendah. Sisi kanan a + b * c dievaluasi penuh (25) sebelum ditambahkan ke x. Tanpa precedence yang benar, ekspresi akan ambigu.
  10. 1..10 step 2 menghasilkan progresi 1, 3, 5, 7, 9. Operator step adalah infix function berada di tingkat infix (lebih rendah dari ..). Ekspresi dievaluasi sebagai (1..10) step 2, bukan 1..(10 step 2).

Asosiativitas Right: Range dan Assignment #

Dua kategori operator yang bersifat right-associative:

  • Range operator ..: a..b..c dievaluasi sebagai a..(b..c) secara konseptual, meskipun dalam praktik jarang ditulis berturut-turut.
  • Assignment =, +=, dst.: a = b = c dievaluasi sebagai a = (b = c) di banyak bahasa; Kotlin membatasi chained assignment karena setiap penugasan harus berdiri sendiri sebagai statement.

Pada operator elvis ?:, sifat right-associative terasa alami: a ?: b ?: c ?: d berarti ambil a jika tidak null, jika null ambil b, jika b null ambil c, jika c null ambil d. Bentuk ini lebih natural dibanding jika elvis bersifat left-associative yang akan mengevaluasi (a ?: b) ?: (c ?: d) — meskipun hasil akhirnya sama untuk null checking, pola penulisan right lebih mudah dibaca.

Aturan Bonus: Infix Function Precedence #

Function infix berada di tingkat 7, di antara .. dan operator elvis. Akibatnya, 1..10 contains 5 dievaluasi sebagai (1..10) contains 5, bukan 1..(10 contains 5). Hal ini konsisten dengan ekspektasi bahwa rentang dibentuk dulu sebelum pemeriksaan keanggotaan.

Referensi Lanjutan #

Dengan menguasai tabel precedence dan asosiativitas, kita dapat menulis ekspresi yang panjang dengan keyakinan akan urutan evaluasi yang benar. Selanjutnya, kita akan mengulas perbedaan mendasar antara expression dan statement di Kotlin, yang akan memperjelas mengapa if dan when dapat mengembalikan nilai.