Kotlin menggabungkan keamanan tipe statis dengan keringkasan sintaks. Salah satu fitur kunci adalah type inference — kompilator menentukan tipe variabel dari nilai yang diberikan tanpa kita menuliskannya. Hal ini membuat kode tetap ringkas namun tetap aman karena tipe setiap variabel sudah dikenal saat kompilasi. Pada chapter ini kita akan melihat bagaimana inferensi bekerja, kapan kita perlu menambah tipe eksplisit, serta beberapa batasan yang perlu dipahami.

Cara kerja type inference #

Saat kita menulis val x = 5, kompilator memeriksa literal 5 lalu menyimpulkan tipe Int. Saat menulis val s = "halo", kompilator menyimpulkan tipe String. Inferensi terjadi pada saat kompilasi — hasilnya tetap aman secara tipe, hanya saja kita tidak perlu menuliskan tipe secara eksplisit.

Tabel berikut merangkum tipe yang disimpulkan dari literal umum:

LiteralTipe disimpulkan
5Int
5LLong
5.0Double
5.0fFloat
'A'Char
"halo"String
trueBoolean
listOf(1, 2, 3)List<Int>
"Budi" to 25Pair<String, Int>

Demonstrasi type inference #

Berikut program yang memperlihatkan tipe yang disimpulkan kompilator untuk berbagai literal:

kotlin
fun main() {
    val umur = 25
    val berat = 65.5
    val nama = "Budi"
    val aktif = true
    val huruf = 'K'
    val daftar = listOf(1, 2, 3)
    val pasangan = "Budi" to "Siti"

    println("umur    = $umur    (${umur.javaClass.simpleName})")
    println("berat   = $berat (${berat.javaClass.simpleName})")
    println("nama    = $nama    (${nama.javaClass.simpleName})")
    println("aktif   = $aktif   (${aktif.javaClass.simpleName})")
    println("huruf   = $huruf    (${huruf.javaClass.simpleName})")
    println("daftar  = $daftar  (${daftar.javaClass.simpleName})")
    println("pasangan= $pasangan (${pasangan.javaClass.simpleName})")

    val kosong = emptyList<Int>()
    println("kosong  = $kosong (${kosong.javaClass.simpleName})")

    val total: Long = 1_000_000L
    println("total   = $total (${total.javaClass.simpleName})")
}

Output yang Dihasilkan:

text
umur    = 25    (int)
berat   = 65.5 (double)
nama    = Budi    (String)
aktif   = true   (boolean)
huruf   = K    (char)
daftar  = [1, 2, 3]  (ArrayList)
pasangan= (Budi, Siti) (Pair)
kosong  = [] (EmptyList)
total   = 1000000 (long)

Mari kita amati baris-baris penting pada kode di atas:

  1. Baris 2 (val umur = 25) — literal 25 adalah bilangan bulat dalam rentang Int, sehingga kompilator menyimpulkan Int. Untuk Long kita perlu suffix L (lihat baris 16).
  2. Baris 3 (val berat = 65.5) — literal 65.5 adalah bilangan desimal, disimpulkan sebagai Double. Untuk Float gunakan suffix f.
  3. Baris 6 (val daftar = listOf(1, 2, 3)) — listOf adalah function generik; kompilator menyimpulkan tipe parameter Int dari elemen list.
  4. Baris 7 (val pasangan = "Budi" to "Siti") — to adalah infix function yang mengembalikan Pair<String, String>.
  5. Baris 11 (val kosong = emptyList<Int>()) — emptyList tidak punya elemen untuk disimpulkan, sehingga tipe parameter wajib diberi eksplisit (<Int>).
  6. Baris 14 (val total: Long = 1_000_000L) — 1_000_000L adalah literal Long; tipe eksplisit : Long bersifat opsional di sini, tetapi berguna untuk dokumentasi.

Kapan menambah tipe eksplisit #

Meskipun inferensi sangat membantu, terdapat situasi di mana tipe eksplisit diperlukan atau dianjurkan:

  • Inferensi tidak dapat menentukan tipe: misalnya emptyList<Int>() tanpa argumen elemen.
  • Kita ingin tipe yang lebih luas: val umur: Number = 25 alih-alih Int.
  • Klarifikasi niat: tipe eksplisit membantu pembaca memahami batasan yang kita inginkan.
  • API publik pada library: tipe eksplisit pada signature function publik membuat kontrak lebih jelas dan stabil.
kotlin Kapan tipe eksplisit membantu
val daftarKosong = emptyList<String>()      // wajib eksplisit
val id: Long = 42                            // eksplisit memaksa Long, bukan Int
val harga: Number = 99.99                    // eksplisit memakai superclass Number
fun hitung(a: Int, b: Int): Int = a + b       // signature publik wajib eksplisit

Batasan type inference #

Inferensi Kotlin hanya bekerja pada level lokal. Beberapa batasan:

  • Tidak menyimpulkan tipe dari penggunaan: kompilator hanya melihat sisi kanan =, bukan cara variabel dipakai nanti.
  • Tidak otomatis widening: val x: Long = 5 sah karena konversi eksplisit dilakukan otomatis untuk literal numerik; tetapi val y: Long = someInt gagal kompiler tanpa .toLong().
  • Lambda parameter: parameter lambda kadang perlu tipe eksplisit jika konteks tidak cukup membantu.

Referensi Lanjutan #

  • Basic types — pengantar tipe dasar Kotlin yang memperkenalkan type inference bawaan kompilator

Inferensi tipe membuat kode Kotlin ringkas tanpa mengurangi keamanan. Selanjutnya, kita akan melihat bagaimana tipe-tipe ini dipetakan ke primitive types atau reference types JVM — pembahasan tuntas pada chapter Primitive Types vs Reference Types.