Sepanjang section ini kita telah mengenal beragam alat untuk memproses teks: String, StringBuilder, raw string, Regex, split/joinToString, dan method Char. Memilih alat yang tepat untuk situasi yang tepat menentukan apakah kode berjalan cepat dan mudah dibaca atau sebaliknya. Pada chapter penutup ini kita akan merangkum praktik terbaik yang sering muncul di kode produksi: menyimpan Regex di variabel untuk dipakai ulang, memilih buildString untuk konkatenasi dinamis, berhati-hati dengan Unicode normalization, dan memilih String.format ketika output perlu format kolom tetap. Pemahaman rangkuman ini menjadi rujukan cepat dalam menulis kode teks yang sehat.
Simpan Regex di Variabel untuk Dipakai Ulang #
Kompilasi pola Regex memerlukan overhead. Memanggil Regex("\\d+") di dalam loop membuat instance baru di setiap iterasi, yang membuang CPU dan memori. Praktik yang benar adalah membuat instance sekali dan menyimpannya di variabel atau properti:
// Praktik buruk
fun hitungAngka(teksList: List<String>): Int {
return teksList.sumOf { teks ->
Regex("\\d+").findAll(teks).count() // kompilasi tiap pemanggilan
}
}
// Praktik baik
val polaAngka = Regex("\\d+") // kompilasi sekali
fun hitungAngkaBaik(teksList: List<String>): Int {
return teksList.sumOf { teks ->
polaAngka.findAll(teks).count()
}
}Untuk class, simpan Regex di companion object agar instance dibagikan antar instance class. Untuk top-level function, simpan di properti file-level private val.
Gunakan buildString untuk Konkatenasi Dinamis #
Ketika kita perlu menyusun string dari banyak bagian yang tidak bisa dinyatakan dengan satu template, buildString adalah pilihan paling ringkas. Function ini membuat instance StringBuilder internal dan mengeksekusi lambda dengan this merujuk ke builder tersebut. Hasilnya dikembalikan sebagai String:
val laporan = buildString {
appendLine("Laporan Penjualan")
appendLine("==============")
repeat(3) { i ->
appendLine(" Item ${i + 1}: ${i * 100} unit")
}
appendLine("Total: 300 unit")
}
println(laporan)Method appendLine() (dengan Line suffix) menambahkan teks dan baris baru otomatis — alternatif yang lebih jelas daripada append("...\n"). Untuk konkatenasi satu baris sederhana, tetap gunakan string template "$a $b $c".
Perhatikan Unicode Normalization #
Beberapa karakter Unicode memiliki beberapa representasi yang berbeda secara visual tetapi berbeda urutan code point. Contoh klasik adalah karakter é yang bisa ditulis sebagai satu precomposed character (U+00E9) atau sebagai kombinasi e (U+0065) dengan combining acute accent (U+0301). Kedua representasi terlihat sama tetapi memiliki panjang berbeda dan tidak dianggap sama oleh perbandingan string biasa:
val a = "café" // 4 karakter (e dengan acute precomposed)
val b = "cafe\u0301" // 5 karakter (e + combining acute)
println(a == b) // false, padahal visualnya sama
println(a.length) // 4
println(b.length) // 5
val aNorm = java.text.Normalizer.normalize(a, java.text.Normalizer.Form.NFC)
val bNorm = java.text.Normalizer.normalize(b, java.text.Normalizer.Form.NFC)
println(aNorm == bNorm) // true, setelah normalisasi NFCUntuk membandingkan input pengguna dengan nilai di database, terutama yang berisi nama, alamat, atau teks multi-bahasa, normalisasikan kedua sisi ke bentuk NFC atau NFD sebelum perbandingan. Tanpa normalisasi, dua string yang tampak sama bisa dianggap berbeda — sumber bug yang sulit dilacak.
String.format untuk Output Terstruktur #
Untuk pencetakan dengan kolom tetap, alignment, atau presisi desimal, String.format (atau method .format(...) pada string) tetap menjadi alat terbaik. String template tidak menyediakan kontrol lebar kolom dan presisi angka secara langsung:
val data = listOf(
"Budi" to 28,
"Siti" to 30,
"Dewi" to 25
)
for ((nama, umur) in data) {
println(String.format("%-10s : %3d tahun", nama, umur))
}
// Budi : 28 tahun
// Siti : 30 tahun
// Dewi : 25 tahunKombinasikan dengan string template bila perlu menyisipkan hasil format ke dalam teks yang lebih besar: println("Profil: ${"%-10s".format(nama)}").
Hindari Konkatenasi di Loop #
Konkatenasi dengan + di dalam loop adalah anti-pola paling umum dalam pemrosesan teks. Setiap s += "x" menyalin seluruh isi string lama ke instance baru. Untuk loop 10.000 iterasi, total karakter yang disalin bisa mencapai puluhan juta. Selalu ganti dengan StringBuilder atau buildString:
// Praktik buruk
var s = ""
for (i in 1..1000) {
s += "$i," // 1000 instance String baru
}
// Praktik baik
val hasil = buildString {
for (i in 1..1000) {
append("$i,")
}
}Contoh Lengkap dan Outputnya #
File best_practices.kt berikut mempraktikkan seluruh poin di atas:
fun main() {
val polaCache = Regex("\\d{4}")
val teks1 = "Tahun 2026 sudah tiba"
val teks2 = "Kode produk ABC-1234 dan XYZ-5678"
println("Pola ${'$'}{polaCache.pattern} di teks1: ${polaCache.find(teks1)?.value}")
println("Pola ${'$'}{polaCache.pattern} di teks2: ${polaCache.findAll(teks2).map { it.value }.toList()}")
val banyak = 5_000
val sb = StringBuilder()
for (i in 1..banyak) {
sb.append("baris-")
sb.append(i)
sb.append("\n")
}
val hasilSB = sb.toString()
println("Panjang buildString: ${hasilSB.length}")
println("Baris pertama: ${hasilSB.lineSequence().first()}")
val buildStringHasil = buildString {
append("Header: laporan\n")
repeat(3) { i ->
append(" baris ke-${i + 1}\n")
}
append("Footer: selesai")
}
println("Hasil buildString:")
println(buildStringHasil)
val nama = "Budi"
val umur = 28
val tinggi = 1.68
val formatOutput = String.format("%-10s : %d tahun, %.2f m", nama, umur, tinggi)
println("Format output:")
println(formatOutput)
val daftar = listOf("Budi", "Siti", "Dewi")
val csv = daftar.joinToString(",")
println("CSV: $csv")
val balik = csv.split(",").joinToString(" | ") { it.uppercase() }
println("Split + join: $balik")
val a = "café"
val b = "cafe\u0301"
println("Panjang a: ${a.length}, Panjang b: ${b.length}")
println("a == b? ${a == b}")
val aNorm = java.text.Normalizer.normalize(a, java.text.Normalizer.Form.NFC)
val bNorm = java.text.Normalizer.normalize(b, java.text.Normalizer.Form.NFC)
println("Setelah NFC: a == b? ${aNorm == bNorm}")
println("Panjang setelah NFC: a=${aNorm.length}, b=${bNorm.length}")
val masukan = " Budi Andi Siti "
val rapi = masukan.trim().split(Regex("\\s+")).joinToString(", ")
println("Rapi: $rapi")
}Output yang Dihasilkan:
Pola ${polaCache.pattern} di teks1: 2026
Pola ${polaCache.pattern} di teks2: [1234, 5678]
Panjang buildString: 53893
Baris pertama: baris-1
Hasil buildString:
Header: laporan
baris ke-1
baris ke-2
baris ke-3
Footer: selesai
Format output:
Budi : 28 tahun, 1.68 m
CSV: Budi,Siti,Dewi
Split + join: BUDI | SITI | DEWI
Panjang a: 4, Panjang b: 5
a == b? false
Setelah NFC: a == b? true
Panjang setelah NFC: a=4, b=4
Rapi: Budi, Andi, SitiMari kita telaah bagian penting dari kode di atas. Baris val polaCache = Regex("\\d{4}") menyimpan instance Regex di variabel sehingga tidak ada kompilasi ulang ketika dipakai di find dan findAll. Bagian val sb = StringBuilder() diikuti loop append menunjukkan pola efisien untuk menyusun teks panjang; hasil toString() menghasilkan string dengan panjang 53.893 karakter dari 5.000 baris. Bagian buildString { ... } menunjukkan alternatif yang lebih ringkas dengan lambda receiver — this di dalam lambda merujuk ke StringBuilder baru. Method String.format("%-10s : %d tahun, %.2f m", ...) menghasilkan output dengan kolom nama rata kiri lebar 10, umur sebagai integer, dan tinggi dengan dua desimal. Bagian Unicode normalization memperlihatkan bahwa "café" (4 karakter) dan "cafe\u0301" (5 karakter) tidak sama sebelum normalisasi NFC, tetapi sama setelahnya. Pipeline terakhir trim().split(Regex("\\s+")).joinToString(", ") menunjukkan pola split-proses-join untuk merapikan input pengguna.
Ringkasan Praktik Terbaik #
| Praktik | Saran |
|---|---|
| Konkatenasi satu baris | String template "$a $b" |
| Konkatenasi dinamis/loop | StringBuilder atau buildString |
| Pola regex dipakai berulang | Simpan Regex di variabel/properti |
| Pemformatan kolom/angka | String.format atau .format(...) |
| Membandingkan teks multi-bahasa | Normalisasi NFC sebelum == |
| Pemisahan multi-delimiter | split(Regex(...)) |
| Penggabungan dengan transformasi | joinToString(transform = { ... }) |
| Teks panjang multi-baris | Raw string + trimIndent |
Referensi Lanjutan #
- Strings — ringkasan resmi pola pemrosesan string di Kotlin
- kotlin.text package
— API reference paket
kotlin.textuntuk utilitas text lengkap
Penutup #
Section String dan Text Processing kini lengkap. Kita mulai dari fondasi String dan Char, memahami immutability, lalu naik ke template, method standar, dan StringBuilder untuk efisiensi. Raw string dan trimIndent/trimMargin membantu menyusun teks terstruktur, sementara Regex dan split/joinToString menjadi alat untuk parsing dan transformasi yang lebih kompleks. Bab-bab berikutnya akan membawa kita ke topik baru: struktur data berupa collection — List, Set, dan Map — yang sering menjadi wadah hasil pemrosesan teks yang baru saja kita pelajari.