Tipe Data #
Setiap nilai dalam program punya tipe — dan tipe menentukan apa yang bisa kamu lakukan dengan nilai itu. Di Java, ada perbedaan mendasar antara tipe primitif (int, double, boolean) dan tipe objek (Integer, Double, Boolean). Kotlin menghapus perbedaan ini: semua tipe adalah objek. Tapi jangan khawatir soal performa — compiler Kotlin cukup cerdas untuk menggunakan representasi primitif JVM di balik layar saat memungkinkan. Hasilnya: kode lebih bersih tanpa boxing/unboxing manual, dengan performa yang setara. Artikel ini membahas seluruh tipe data bawaan Kotlin secara mendalam, termasuk cara konversi, operasi yang tersedia, dan tipe-tipe khusus yang tidak ada di bahasa lain.
Tipe Numerik #
Kotlin menyediakan enam tipe numerik: empat untuk bilangan bulat dan dua untuk bilangan desimal.
Bilangan Bulat (Integer) #
| Tipe | Ukuran | Rentang Nilai |
|---|---|---|
Byte | 8 bit | -128 sampai 127 |
Short | 16 bit | -32.768 sampai 32.767 |
Int | 32 bit | -2.147.483.648 sampai 2.147.483.647 (~2,1 miliar) |
Long | 64 bit | -9,2 × 10¹⁸ sampai 9,2 × 10¹⁸ |
Untuk kebanyakan keperluan sehari-hari, Int sudah mencukupi. Gunakan Long untuk nilai yang melebihi kapasitas Int — seperti timestamp Unix dalam milidetik, ID database yang sangat besar, atau hitungan byte file besar.
val umur: Byte = 25
val tahun: Short = 2024
val populasiKota: Int = 10_500_000
val timestampMs: Long = System.currentTimeMillis()
// Suffix L untuk Long literal
val jarakBintangMeter = 9_461_000_000_000_000L // 1 tahun cahaya dalam meter
Kotlin mendukung underscore sebagai pemisah digit untuk keterbacaan — compiler mengabaikannya sepenuhnya:
val sejuta = 1_000_000
val hexWarna = 0xFF_33_AA
val biner = 0b1010_1100_1111_0000
val oktalbytes = 255
Bilangan Desimal (Floating-Point) #
| Tipe | Ukuran | Presisi | Rentang Perkiraan |
|---|---|---|---|
Float | 32 bit | ~6–7 digit desimal | ±3.4 × 10³⁸ |
Double | 64 bit | ~15–16 digit desimal | ±1.7 × 10³⁰⁸ |
Double adalah default — literal 3.14 tanpa suffix diinfer sebagai Double. Gunakan suffix f atau F untuk Float:
val suhu: Double = 36.6 // Double (default)
val suhuFloat: Float = 36.6f // Float — butuh suffix f
val phi = 3.141592653589793 // Double — presisi penuh
val phiFloat = 3.1415927f // Float — presisi terpotong
Jangan gunakan
FloatatauDoubleuntuk nilai keuangan (uang). Representasi floating-point tidak bisa merepresentasikan semua angka desimal secara tepat, sehingga perhitungan uang bisa menghasilkan error kecil yang terakumulasi. GunakanBigDecimaluntuk uang.// ANTI-PATTERN: perhitungan uang dengan Double val harga = 0.1 + 0.2 println(harga) // 0.30000000000000004 — bukan 0.3! // BENAR: gunakan BigDecimal import java.math.BigDecimal val hargaTepat = BigDecimal("0.1") + BigDecimal("0.2") println(hargaTepat) // 0.3
Konversi Tipe Numerik #
Kotlin tidak melakukan konversi numerik implisit — berbeda dari Java. Kamu harus selalu konversi secara eksplisit menggunakan fungsi konversi yang tersedia.
val angkaInt: Int = 42
// ANTI-PATTERN: assignment langsung antar tipe numerik
val angkaLong: Long = angkaInt // ✗ error kompilasi di Kotlin!
// BENAR: konversi eksplisit
val angkaLong: Long = angkaInt.toLong()
val angkaDouble: Double = angkaInt.toDouble()
val angkaFloat: Float = angkaInt.toFloat()
val angkaByte: Byte = angkaInt.toByte()
val angkaShort: Short = angkaInt.toShort()
Fungsi konversi yang tersedia untuk semua tipe numerik:
| Fungsi | Hasil |
|---|---|
.toByte() | Byte |
.toShort() | Short |
.toInt() | Int |
.toLong() | Long |
.toFloat() | Float |
.toDouble() | Double |
.toChar() | Char |
.toString() | String |
Konversi String ke Angka #
val teks = "42"
val angka = teks.toInt() // 42
val desimal = "3.14".toDouble() // 3.14
// Konversi aman — kembalikan null jika gagal, bukan exception
val valid = "123".toIntOrNull() // 123
val invalid = "abc".toIntOrNull() // null
val gagal = "99.9".toIntOrNull() // null (bukan integer)
// Gunakan Elvis untuk nilai default
val nilai = "bukan angka".toIntOrNull() ?: 0 // 0
Overflow — Perhatikan Batasnya #
val maks = Int.MAX_VALUE // 2_147_483_647
val overflow = maks + 1 // -2_147_483_648 — bukan error, tapi salah!
// BENAR: gunakan Long jika nilai bisa melebihi Int.MAX_VALUE
val maksAman = Int.MAX_VALUE.toLong() + 1 // 2_147_483_648L
Char — Tipe Karakter #
Char menyimpan satu karakter Unicode. Ditulis dengan tanda kutip tunggal.
val huruf: Char = 'A'
val angkaKarakter: Char = '7'
val simbol: Char = '@'
val unicode: Char = '\u0041' // 'A' dalam notasi Unicode escape
println(huruf.code) // 65 — nilai kode ASCII/Unicode
println(huruf.isLetter()) // true
println(huruf.isUpperCase()) // true
println(huruf.lowercaseChar()) // 'a'
Char di Kotlin adalah tipe tersendiri — bukan angka. Di Java, char bisa digunakan langsung sebagai int. Di Kotlin, konversi harus eksplisit:
val karakter = 'A'
// ANTI-PATTERN: perlakukan Char sebagai angka langsung
val kode: Int = karakter // ✗ error kompilasi
// BENAR: konversi eksplisit
val kode: Int = karakter.code // 65
val kodeChar: Char = 65.toChar() // 'A'
Escape Character #
val newline = '\n' // baris baru
val tab = '\t' // tab
val backslash = '\\' // backslash
val kutipTunggal = '\'' // tanda kutip tunggal
val unicode = '\u2764' // ❤ — karakter Unicode
Boolean — Tipe Logika #
Boolean hanya punya dua nilai: true dan false. Digunakan untuk kondisi, flag, dan kontrol alur.
val aktif: Boolean = true
val sudahVerifikasi = false // type inference
// Operator logika
val danLogika = true && false // false — AND
val atauLogika = true || false // true — OR
val negasi = !true // false — NOT
// Short-circuit evaluation
// && berhenti evaluasi jika kiri sudah false
// || berhenti evaluasi jika kiri sudah true
val aman = daftar.isNotEmpty() && daftar[0] != null
Fungsi Ekstensi Boolean yang Berguna #
val kondisi = 10 > 5
// takeIf dan takeUnless
val nilai = kondisi.takeIf { it } // true jika kondisi true, null jika false
// Konversi ke String
println(kondisi.toString()) // "true"
String — Tipe Teks #
String menyimpan urutan karakter yang bersifat immutable. Setiap operasi pada string menghasilkan string baru, bukan mengubah yang sudah ada.
val salam = "Halo, Dunia!"
val kosong = ""
val spasi = " "
println(salam.length) // 12
println(salam.uppercase()) // HALO, DUNIA!
println(salam.lowercase()) // halo, dunia!
println(salam.reversed()) // !ainuD ,olaH
println(salam.isEmpty()) // false
println(kosong.isEmpty()) // true
println(spasi.isBlank()) // true — isBlank() cek isEmpty() atau hanya spasi
String Template #
val nama = "Budi"
val umur = 28
// Interpolasi variabel
val perkenalan = "Nama saya $nama, umur $umur tahun."
// Interpolasi ekspresi
val info = "Panjang nama: ${nama.length} karakter"
val status = "Status: ${if (umur >= 18) "Dewasa" else "Anak-anak"}"
val perhitungan = "Dua kali umur: ${umur * 2}"
Raw String (Multiline) #
Raw string menggunakan triple quote dan mempertahankan semua karakter termasuk newline dan tab — tanpa perlu escape.
val json = """
{
"nama": "Budi Santoso",
"umur": 28,
"kota": "Jakarta"
}
""".trimIndent()
val query = """
SELECT u.nama, u.email
FROM pengguna u
WHERE u.aktif = true
AND u.umur >= 18
ORDER BY u.nama
""".trimIndent()
println(json)
.trimIndent() menghapus indentasi leading yang sama di semua baris — hasilnya string tanpa indentasi berlebih.
Operasi String yang Sering Digunakan #
val teks = " Kotlin Programming "
// Pembersihan
println(teks.trim()) // "Kotlin Programming"
println(teks.trimStart()) // "Kotlin Programming "
println(teks.trimEnd()) // " Kotlin Programming"
// Pemeriksaan
println(teks.contains("Kotlin")) // true
println(teks.startsWith(" Kot")) // true
println(teks.endsWith("ing ")) // true
// Manipulasi
println(teks.replace("Kotlin", "Swift")) // " Swift Programming "
println(teks.trim().split(" ")) // [Kotlin, Programming]
val kata = "kotlin"
println(kata.capitalize()) // Kotlin (deprecated di versi terbaru)
println(kata.replaceFirstChar { it.uppercase() }) // Kotlin (cara modern)
// Substring
val bahasa = "Kotlin Programming"
println(bahasa.substring(0, 6)) // Kotlin
println(bahasa.substringAfter(" ")) // Programming
println(bahasa.substringBefore(" ")) // Kotlin
String Immutability dan StringBuilder #
Karena String immutable, operasi berulang yang membangun string (seperti dalam loop) sebaiknya menggunakan StringBuilder:
// ANTI-PATTERN: konkatenasi string dalam loop — buat objek baru setiap iterasi
var hasil = ""
for (i in 1..1000) {
hasil += "item-$i," // sangat tidak efisien untuk loop besar
}
// BENAR: gunakan StringBuilder
val builder = StringBuilder()
for (i in 1..1000) {
builder.append("item-$i,")
}
val hasil = builder.toString()
// Atau gunakan joinToString untuk koleksi
val hasil = (1..1000).joinToString(",") { "item-$it" }
Array #
Array adalah struktur data berukuran tetap yang menyimpan elemen-elemen dari tipe yang sama. Ukurannya tidak bisa diubah setelah dibuat.
// Array generik
val buah: Array<String> = arrayOf("Mangga", "Apel", "Jeruk")
val angka: Array<Int> = arrayOf(1, 2, 3, 4, 5)
// Array dengan ukuran dan nilai default
val nol = Array(5) { 0 } // [0, 0, 0, 0, 0]
val kuadrat = Array(5) { i -> i * i } // [0, 1, 4, 9, 16]
// Akses elemen
println(buah[0]) // Mangga
println(buah.size) // 3
buah[1] = "Pisang" // modifikasi nilai
println(buah[1]) // Pisang
Array Primitif — Lebih Efisien #
Untuk tipe numerik, Kotlin menyediakan array primitif yang tidak melakukan boxing — performanya setara dengan array Java primitif:
val byteArray = byteArrayOf(1, 2, 3)
val shortArray = shortArrayOf(10, 20, 30)
val intArray = intArrayOf(1, 2, 3, 4, 5)
val longArray = longArrayOf(1L, 2L, 3L)
val floatArray = floatArrayOf(1.0f, 2.5f, 3.7f)
val doubleArray = doubleArrayOf(1.0, 2.5, 3.14)
val booleanArray = booleanArrayOf(true, false, true)
val charArray = charArrayOf('K', 'o', 't', 'l', 'i', 'n')
Array vs List — Kapan Memilih #
GUNAKAN Array jika:
✓ Bekerja dengan API Java yang membutuhkan array
✓ Performa kritis dan kamu butuh array primitif
✓ Ukuran sudah pasti dan tidak berubah
✓ Interoperabilitas dengan kode Java
GUNAKAN List jika:
✓ Ukuran mungkin berubah (MutableList)
✓ Butuh operasi koleksi: filter, map, sortedBy, dll
✓ Kode yang lebih ekspresif dan idiomatic Kotlin
✓ Mayoritas use case sehari-hari
Tipe Khusus: Any, Unit, Nothing #
Ini adalah tiga tipe yang ada di puncak dan dasar hierarki tipe Kotlin — dan masing-masing punya peran unik.
flowchart TD
A["Any\n(supertype semua tipe non-null)"] --> B[String]
A --> C[Int]
A --> D[Boolean]
A --> E[Kelas kustom...]
F["Any?\n(supertype semua tipe termasuk null)"] --> A
F --> G[null]
H["Nothing\n(subtype semua tipe — tidak pernah ada nilainya)"] --> B
H --> C
H --> DAny — Supertype Semua Tipe
#
Any adalah induk dari semua tipe non-nullable di Kotlin. Setara dengan Object di Java, tapi lebih bersih karena tidak harus bergulat dengan primitif.
val apa: Any = "Bisa berisi apa saja"
val juga: Any = 42
val atau: Any = true
val bahkan: Any = listOf(1, 2, 3)
// Any punya tiga method dasar
println(apa.toString()) // representasi string
println(apa.hashCode()) // hash code
println(apa.equals("test")) // perbandingan kesetaraan
// Type check dan smart cast
fun deskripsikan(nilai: Any): String {
return when (nilai) {
is String -> "String dengan panjang ${nilai.length}"
is Int -> "Integer: ${nilai * 2}"
is Boolean -> "Boolean: ${if (nilai) "benar" else "salah"}"
is List<*> -> "List dengan ${nilai.size} elemen"
else -> "Tipe tidak dikenal: ${nilai::class.simpleName}"
}
}
println(deskripsikan("Kotlin")) // String dengan panjang 6
println(deskripsikan(21)) // Integer: 42
println(deskripsikan(true)) // Boolean: benar
Unit — Pengganti void
#
Unit adalah tipe return untuk fungsi yang tidak mengembalikan nilai bermakna. Ini setara dengan void di Java, tapi Unit adalah tipe nyata — bisa disimpan dalam variabel dan digunakan sebagai type argument generik.
// Dua cara ini identik — Unit bisa ditulis eksplisit atau dihilangkan
fun cetakPesan(pesan: String): Unit {
println(pesan)
}
fun cetakPesan2(pesan: String) { // Unit tersirat
println(pesan)
}
// Unit berguna dalam generik
val aksi: () -> Unit = { println("Dieksekusi!") }
val daftar: List<() -> Unit> = listOf(
{ println("Aksi 1") },
{ println("Aksi 2") }
)
daftar.forEach { it() }
Nothing — Tipe yang Tidak Pernah Ada
#
Nothing adalah tipe yang tidak pernah memiliki instance. Ia adalah subtype dari semua tipe lain, yang artinya bisa digunakan di mana pun tipe apapun diharapkan. Digunakan untuk fungsi yang tidak pernah kembali secara normal — selalu melempar exception atau berjalan selamanya.
// Fungsi yang selalu melempar exception
fun gagal(pesan: String): Nothing {
throw IllegalStateException(pesan)
}
// Berguna untuk validasi yang mengakhiri eksekusi
fun ambilNilai(map: Map<String, Int>, kunci: String): Int {
return map[kunci] ?: gagal("Kunci '$kunci' tidak ditemukan")
// Compiler tahu: jika map[kunci] null, gagal() tidak pernah return
// Sehingga ekspresi ini selalu menghasilkan Int — aman!
}
// Fungsi yang berjalan selamanya
fun loopSelamanya(): Nothing {
while (true) {
Thread.sleep(1000)
println("Masih berjalan...")
}
}
Nothing juga digunakan sebagai tipe dari ekspresi throw:
val nama: String = ambilNamaDariDb()
?: throw IllegalStateException("Nama harus ada")
// Tipe ekspresi throw adalah Nothing
// Nothing adalah subtype String
// Sehingga ekspresi keseluruhan bertipe String ✓
Sistem Tipe Kotlin: Non-Nullable dan Nullable #
Semua tipe di Kotlin hadir dalam dua versi: non-nullable (default) dan nullable (dengan ?).
flowchart LR
A["String\n(tidak bisa null)"] -- "tambahkan ?" --> B["String?\n(bisa null atau String)"]
C["Int\n(tidak bisa null)"] -- "tambahkan ?" --> D["Int?\n(bisa null atau Int)"]
E["Any\n(semua non-null)"] -- "tambahkan ?" --> F["Any?\n(semua tipe termasuk null)"]// Non-nullable — compiler menjamin tidak pernah null
var nama: String = "Budi"
var umur: Int = 25
// Nullable — harus ditangani sebelum digunakan
var alamat: String? = null
var nilaiOpsional: Int? = null
// Type checking dengan is
fun prosesNilai(nilai: Any?) {
when {
nilai == null -> println("Null")
nilai is String -> println("String: $nilai")
nilai is Int -> println("Int: $nilai")
nilai is Double -> println("Double: $nilai")
else -> println("Tipe lain: ${nilai::class.simpleName}")
}
}
Type Casting Aman #
val nilai: Any = "Kotlin"
// Safe cast — kembalikan null jika gagal (bukan exception)
val teks: String? = nilai as? String // "Kotlin"
val angka: Int? = nilai as? Int // null — bukan Int
// Unsafe cast — exception jika gagal
val teks2: String = nilai as String // OK
// val angka2: Int = nilai as Int // ✗ ClassCastException!
Ringkasan #
- Enam tipe numerik —
Byte,Short,Int,Longuntuk bilangan bulat;Float,Doubleuntuk desimal.IntdanDoubleadalah default untuk masing-masing kategori.- Tidak ada konversi implisit — Kotlin memaksa konversi eksplisit antar tipe numerik dengan
.toInt(),.toLong(),.toDouble(), dll. Ini mencegah bug yang tidak disengaja.- Gunakan
BigDecimaluntuk uang —FloatdanDoubletidak bisa merepresentasikan semua desimal secara tepat; perhitungan keuangan harus menggunakanBigDecimal.Charbukan angka — berbeda dari Java,Chardi Kotlin adalah tipe tersendiri. Konversi ke angka menggunakan.code, konversi sebaliknya dengan.toChar().Stringimmutable — setiap operasi string menghasilkan objek baru. Untuk operasi berulang dalam loop, gunakanStringBuilderataujoinToString().- Array vs List —
Arrayuntuk interop Java dan performa kritis;Listuntuk kode Kotlin sehari-hari karena lebih ekspresif dengan dukungan operasi koleksi lengkap.Anyadalah supertype — semua tipe non-nullable mewarisi dariAny.Any?mencakup juga nilai null.Unitadalahvoidyang nyata — tipe return fungsi tanpa nilai bermakna. Berbeda darivoid,Unitbisa digunakan sebagai type argument generik.Nothingtidak pernah ada — tipe fungsi yang tidak pernah return normal (selalu throw atau loop selamanya). Berguna sebagai sinyal kepada compiler bahwa cabang kode tertentu tidak bisa dicapai.- Setiap tipe hadir dalam dua versi — non-nullable (default, aman) dan nullable (dengan
?, harus ditangani). Pilih non-nullable sebagai default; tambahkan?hanya jika null memang valid secara semantik.