Characters #
Char adalah tipe yang sering diabaikan — kebanyakan developer langsung bekerja dengan String tanpa pernah menyentuh karakter individual. Padahal ada banyak situasi di mana bekerja di level karakter jauh lebih tepat: memvalidasi format input karakter per karakter, membangun parser sederhana, mengklasifikasikan isi teks, atau memanipulasi data biner. Kotlin menyediakan API Char yang kaya — jauh lebih bersih dari Java yang masih mengandalkan perbandingan integer dan method statis Character. Artikel ini membahas tipe Char secara menyeluruh: representasinya sebagai Unicode, fungsi klasifikasi bawaan, operasi yang bisa dilakukan, cara kerjanya dengan String, dan pola idiomatik untuk pemrosesan teks di level karakter.
Char sebagai Tipe #
Di Kotlin, Char adalah tipe tersendiri — bukan integer, bukan byte. Ini berbeda dari C/C++ di mana char pada dasarnya adalah angka kecil. Kotlin memisahkan Char dari Int secara tegas di level sistem tipe.
// Membuat Char
val huruf: Char = 'A'
val angka: Char = '7'
val spasi: Char = ' '
val newline: Char = '\n'
// Char dan Int adalah tipe BERBEDA di Kotlin
val c: Char = 'A'
// val n: Int = c // ERROR: Type mismatch
// Konversi eksplisit diperlukan
val kode: Int = c.code // 65 — kode Unicode/ASCII
val kembali: Char = 65.toChar() // 'A'
// Perbandingan langsung tidak bisa antar tipe
// println(c == 65) // ERROR
println(c.code == 65) // true
println(c == 'A') // true
// Char mendukung operator relasional
println('A' < 'Z') // true (berdasarkan kode Unicode)
println('a' > 'A') // true ('a' = 97, 'A' = 65)
println('0' < '9') // true
flowchart LR
A["Char 'A'"] --> B["Representasi Unicode\nU+0041"]
B --> C["Kode numerik\n.code = 65"]
C --> D["Kembali ke Char\n65.toChar() = 'A'"]
A --> E["String\n.toString() = \"A\""]
A --> F["Operasi relasional\n'A' < 'Z' → true"]Escape Characters #
// Karakter escape yang tersedia di Kotlin
val tab = '\t' // Tab horizontal
val newline = '\n' // Newline (Line Feed)
val cr = '\r' // Carriage Return
val backslash = '\\' // Backslash
val petik = '\'' // Single quote
val petikGanda = '\"' // Double quote (opsional dalam Char, wajib dalam String)
val null_char = '\u0000' // Null character
val unicode = '\u03B1' // α — huruf Greek alpha
// Unicode escape — format \uXXXX
val bintang = '\u2605' // ★
val hati = '\u2665' // ♥
val centang = '\u2713' // ✓
val silang = '\u2717' // ✗
val derajat = '\u00B0' // °
val euro = '\u20AC' // €
val rupiah = '\u20B9' // ₹ (bukan Rupiah, tapi Rupee India)
println("Suhu: 37${'\u00B0'}C") // Suhu: 37°C
Klasifikasi Karakter #
Ini adalah bagian paling berguna dari Char API — fungsi-fungsi untuk mengklasifikasikan karakter tanpa harus ingat rentang kode ASCII.
Fungsi isXxx #
val c = 'A'
// Klasifikasi huruf
println('A'.isLetter()) // true — semua huruf Unicode
println('5'.isLetter()) // false
println('α'.isLetter()) // true — termasuk huruf non-Latin
println('A'.isUpperCase()) // true
println('a'.isLowerCase()) // true
println('A'.isUpperCase()) // true
println('5'.isUpperCase()) // false
// Klasifikasi angka
println('5'.isDigit()) // true — digit 0-9
println('A'.isDigit()) // false
println('5'.isDigit()) // true
println('٥'.isDigit()) // true — digit Arab, tergantung implementasi
// Gabungan huruf dan angka
println('A'.isLetterOrDigit()) // true
println('5'.isLetterOrDigit()) // true
println('_'.isLetterOrDigit()) // false
println('@'.isLetterOrDigit()) // false
// Whitespace
println(' '.isWhitespace()) // true
println('\t'.isWhitespace()) // true
println('\n'.isWhitespace()) // true
println('A'.isWhitespace()) // false
// Tanda baca dan simbol
println('.'.isLetterOrDigit()) // false
println('_'.isLetterOrDigit()) // false
Tabel Fungsi Klasifikasi Lengkap #
| Fungsi | Deskripsi | Contoh true | Contoh false |
|---|---|---|---|
isLetter() | Huruf Unicode | 'A', 'α', 'あ' | '5', '@' |
isDigit() | Angka 0–9 | '0', '9' | 'A', ' ' |
isLetterOrDigit() | Huruf atau angka | 'A', '5' | '_', '@' |
isUpperCase() | Huruf kapital | 'A', 'Z' | 'a', '5' |
isLowerCase() | Huruf kecil | 'a', 'z' | 'A', '5' |
isWhitespace() | Spasi, tab, newline | ' ', '\t', '\n' | 'A', '_' |
isISOControl() | Karakter kontrol | '\t', '\n' | 'A', ' ' |
Konversi Case #
// Uppercase dan Lowercase
println('a'.uppercaseChar()) // 'A'
println('A'.lowercaseChar()) // 'a'
println('5'.uppercaseChar()) // '5' — angka tidak berubah
println('α'.uppercaseChar()) // 'Α' — huruf Greek juga dikonversi
// titlecase — digunakan untuk huruf awal kata
println('a'.titlecaseChar()) // 'A' — sama dengan uppercase untuk Latin
// Tapi berbeda untuk beberapa skrip Unicode seperti DZ → Dz (titlecase) vs DZ (uppercase)
// Versi lama yang deprecated — hindari
// 'a'.toUpperCase() // deprecated
// 'a'.toLowerCase() // deprecated
// Penggunaan dalam transformasi String
fun String.titleCase(): String = split(" ").joinToString(" ") { kata ->
if (kata.isEmpty()) kata
else kata[0].uppercaseChar() + kata.substring(1).lowercase()
}
println("halo dunia kotlin".titleCase()) // Halo Dunia Kotlin
// Cek apakah perlu konversi
fun String.sudahTitleCase(): Boolean = split(" ").all { kata ->
kata.isEmpty() || (kata[0].isUpperCase() && kata.drop(1).all { !it.isUpperCase() })
}
Operasi Aritmatika pada Char #
Char mendukung operasi aritmatika terbatas — penambahan dan pengurangan dengan Int, serta pengurangan antar Char.
// Char + Int = Char
val a = 'A'
println(a + 1) // 'B'
println(a + 25) // 'Z'
// Char - Int = Char
val z = 'Z'
println(z - 1) // 'Y'
println(z - 25) // 'A'
// Char - Char = Int (jarak antara dua karakter)
println('Z' - 'A') // 25
println('z' - 'a') // 25
println('9' - '0') // 9 — berguna untuk konversi digit ke angka
// Aplikasi: menggeser huruf (Caesar cipher sederhana)
fun geserHuruf(c: Char, geser: Int): Char {
return when {
c.isUpperCase() -> 'A' + (c - 'A' + geser).mod(26)
c.isLowerCase() -> 'a' + (c - 'a' + geser).mod(26)
else -> c // bukan huruf, biarkan apa adanya
}
}
fun enkripsiCaesar(teks: String, geser: Int): String =
teks.map { geserHuruf(it, geser) }.joinToString("")
println(enkripsiCaesar("Halo Dunia", 3)) // Kdor Gxqld
println(enkripsiCaesar("Kdor Gxqld", -3)) // Halo Dunia
// Iterasi alfabet dengan aritmatika
val alfabet = (0..25).map { 'A' + it }
println(alfabet.joinToString("")) // ABCDEFGHIJKLMNOPQRSTUVWXYZ
// Konversi digit karakter ke Int
val digitChar = '7'
val nilaiInt = digitChar - '0' // 7 — cara manual
val nilaiInt2 = digitChar.digitToInt() // 7 — cara idiomatik
// digitToInt dengan basis
'F'.digitToInt(16) // 15 (hex)
'7'.digitToInt(8) // 7 (oktal)
'1'.digitToInt(2) // 1 (biner)
Char dan String #
Char dan String berinteraksi erat — String pada dasarnya adalah urutan Char.
Akses Karakter dalam String #
val teks = "Kotlin"
// Akses by index
val pertama: Char = teks[0] // 'K'
val terakhir: Char = teks[teks.length - 1] // 'n'
val terakhir2: Char = teks.last() // 'n' — lebih idiomatik
// Iterasi karakter
for (c in teks) {
print("$c ") // K o t l i n
}
// forEachIndexed
teks.forEachIndexed { index, char ->
println("$index: $char")
}
// Akses sebagai List<Char>
val chars: List<Char> = teks.toList()
// ['K', 'o', 't', 'l', 'i', 'n']
// CharArray
val charArray: CharArray = teks.toCharArray()
val dariArray: String = String(charArray) // "Kotlin"
// first dan last dengan predikat
val hurufBesar = teks.first { it.isUpperCase() } // 'K'
val hurufKecil = teks.last { it.isLowerCase() } // 'n'
Membangun String dari Char #
// Char ke String
val c = 'A'
val s: String = c.toString() // "A"
val s2: String = "$c" // "A" (string template)
// Membangun String dari List<Char>
val chars = listOf('K', 'o', 't', 'l', 'i', 'n')
val teks = chars.joinToString("") // "Kotlin"
val teks2 = String(chars.toCharArray()) // "Kotlin"
// buildString dengan Char
val hasil = buildString {
append('H')
append('a')
append('l')
append('o')
}
// "Halo"
// Transformasi karakter dalam String
val input = "hElLo WoRlD"
val lowercase = input.map { it.lowercaseChar() }.joinToString("")
// "hello world"
// Atau lebih langsung:
val lowercase2 = input.lowercase()
// Filter karakter tertentu
val hanyaHuruf = "H3ll0 W0r1d!".filter { it.isLetter() }
// "HllWrd"
val hanyaAngka = "tel: 0812-3456-7890".filter { it.isDigit() }
// "081234567890"
val tanpaSpasi = "Kotlin is fun".filterNot { it.isWhitespace() }
// "Kotlinisfun"
Validasi Input dengan Char #
Salah satu penggunaan paling praktis Char API adalah validasi format input — lebih efisien dari regex untuk pola sederhana.
// Validasi nomor telepon sederhana
fun isNomorTeleponValid(nomor: String): Boolean {
val bersih = nomor.filter { it.isDigit() }
return bersih.length in 10..13
}
// Validasi format password
fun validasiPassword(password: String): List<String> {
val error = mutableListOf<String>()
if (password.length < 8) error.add("Minimal 8 karakter")
if (password.none { it.isUpperCase() }) error.add("Harus ada huruf kapital")
if (password.none { it.isLowerCase() }) error.add("Harus ada huruf kecil")
if (password.none { it.isDigit() }) error.add("Harus ada angka")
if (password.none { !it.isLetterOrDigit() }) error.add("Harus ada karakter spesial")
return error
}
val hasil = validasiPassword("Kotlin99")
// ["Harus ada karakter spesial"]
val hasilKuat = validasiPassword("K0tl!n99")
// [] — valid
// Validasi format kartu kredit (16 digit)
fun isKartuKreditValid(nomor: String): Boolean {
val bersih = nomor.filterNot { it == ' ' || it == '-' }
return bersih.length == 16 && bersih.all { it.isDigit() }
}
isKartuKreditValid("4111 1111 1111 1111") // true
isKartuKreditValid("4111-1111-1111-1111") // true
isKartuKreditValid("411111111111111") // false (15 digit)
// Validasi username: huruf, angka, underscore saja
fun isUsernameValid(username: String): Boolean {
if (username.length !in 3..20) return false
if (!username[0].isLetter()) return false // harus diawali huruf
return username.all { it.isLetterOrDigit() || it == '_' }
}
isUsernameValid("andi_123") // true
isUsernameValid("123andi") // false (diawali angka)
isUsernameValid("an") // false (terlalu pendek)
isUsernameValid("andi-user") // false (dash tidak diizinkan)
Parser Sederhana dengan Char #
Level karakter ideal untuk membangun parser ringan tanpa perlu library eksternal.
// Parser ekspresi matematika sederhana — tokenizer
enum class TokenType { ANGKA, PLUS, MINUS, KALI, BAGI, KURUNG_BUKA, KURUNG_TUTUP }
data class Token(val type: TokenType, val nilai: String)
fun tokenize(ekspresi: String): List<Token> {
val tokens = mutableListOf<Token>()
var i = 0
while (i < ekspresi.length) {
val c = ekspresi[i]
when {
c.isWhitespace() -> i++ // lewati spasi
c.isDigit() -> {
val start = i
while (i < ekspresi.length && (ekspresi[i].isDigit() || ekspresi[i] == '.')) {
i++
}
tokens.add(Token(TokenType.ANGKA, ekspresi.substring(start, i)))
}
c == '+' -> { tokens.add(Token(TokenType.PLUS, "+")); i++ }
c == '-' -> { tokens.add(Token(TokenType.MINUS, "-")); i++ }
c == '*' -> { tokens.add(Token(TokenType.KALI, "*")); i++ }
c == '/' -> { tokens.add(Token(TokenType.BAGI, "/")); i++ }
c == '(' -> { tokens.add(Token(TokenType.KURUNG_BUKA, "(")); i++ }
c == ')' -> { tokens.add(Token(TokenType.KURUNG_TUTUP, ")")); i++ }
else -> throw IllegalArgumentException("Karakter tidak dikenal: $c")
}
}
return tokens
}
val tokens = tokenize("12 + 34 * (5 - 2)")
// [Token(ANGKA,"12"), Token(PLUS,"+"), Token(ANGKA,"34"), ...]
// Parser CSV sederhana
fun parseCSVBaris(baris: String, pemisah: Char = ','): List<String> {
val hasil = mutableListOf<String>()
val builder = StringBuilder()
var dalamPetik = false
for (c in baris) {
when {
c == '"' -> dalamPetik = !dalamPetik
c == pemisah && !dalamPetik -> {
hasil.add(builder.toString().trim())
builder.clear()
}
else -> builder.append(c)
}
}
hasil.add(builder.toString().trim())
return hasil
}
val baris = """Andi,"Jakarta, Selatan",25"""
val kolom = parseCSVBaris(baris)
// ["Andi", "Jakarta, Selatan", "25"]
Unicode dan Char Khusus #
Kotlin menggunakan Unicode secara penuh, tapi ada beberapa nuansa penting.
Supplementary Characters (di luar BMP) #
// Char di Kotlin merepresentasikan UTF-16 code unit (16-bit)
// Karakter di luar BMP (Basic Multilingual Plane) butuh dua Char — disebut surrogate pair
val emoji = "😀"
println(emoji.length) // 2 — bukan 1! (dua UTF-16 code units)
println(emoji[0].code) // 55357 — high surrogate
println(emoji[1].code) // 56832 — low surrogate
// Jumlah karakter Unicode yang sebenarnya
println(emoji.codePointCount(0, emoji.length)) // 1
// Cara aman iterasi codepoints
"Halo 😀 Kotlin".codePoints().forEach { cp ->
println(cp.toChar()) // bisa error untuk supplementary, tapi ilustratif
}
// Untuk string dengan emoji/supplementary characters:
val teksEmoji = "Halo 😀 World 🌍"
println(teksEmoji.length) // lebih dari jumlah karakter visual
// Hitung karakter visual dengan benar
fun String.panjangVisual(): Int = codePointCount(0, length)
println(teksEmoji.panjangVisual()) // jumlah codepoint sebenarnya
Char dan Locale #
// Konversi case yang mempertimbangkan locale
val turki = "istanbul"
println(turki.uppercase()) // ISTANBUL (locale default)
println(turki.uppercase(java.util.Locale("tr"))) // İSTANBUL (i → İ, bukan I, dalam Turki)
// Ini penting untuk aplikasi multi-bahasa
// Untuk keamanan, selalu gunakan Locale.ROOT saat mengkonversi untuk perbandingan teknis
val versi = "v1.0.0-BETA"
val versionNormal = versi.lowercase(java.util.Locale.ROOT) // "v1.0.0-beta"
Pola Idiomatik untuk Pemrosesan Karakter #
Menghitung Frekuensi Karakter #
val teks = "kotlin programming language"
// Frekuensi setiap karakter
val frekuensi: Map<Char, Int> = teks
.filter { !it.isWhitespace() }
.groupingBy { it }
.eachCount()
// Karakter paling sering muncul
val terbanyak = frekuensi.maxByOrNull { it.value }
println("'${terbanyak?.key}' muncul ${terbanyak?.value} kali")
// Karakter unik
val unik = teks.filter { !it.isWhitespace() }.toSet()
println("${unik.size} karakter unik")
Anagram Checker #
fun isAnagram(a: String, b: String): Boolean {
val bersihA = a.lowercase().filter { it.isLetter() }
val bersihB = b.lowercase().filter { it.isLetter() }
if (bersihA.length != bersihB.length) return false
return bersihA.groupingBy { it }.eachCount() ==
bersihB.groupingBy { it }.eachCount()
}
println(isAnagram("listen", "silent")) // true
println(isAnagram("Astronomer", "Moon starer")) // true
println(isAnagram("hello", "world")) // false
Palindrome Checker #
fun isPalindrome(teks: String): Boolean {
val bersih = teks.lowercase().filter { it.isLetterOrDigit() }
return bersih == bersih.reversed()
}
isPalindrome("katak") // true
isPalindrome("A man a plan a canal Panama") // true
isPalindrome("kotlin") // false
ROT13 Encoder #
fun rot13(teks: String): String = teks.map { c ->
when {
c in 'A'..'Z' -> 'A' + (c - 'A' + 13) % 26
c in 'a'..'z' -> 'a' + (c - 'a' + 13) % 26
else -> c
}
}.joinToString("")
println(rot13("Hello, World!")) // Uryyb, Jbeyq!
println(rot13("Uryyb, Jbeyq!")) // Hello, World! (simetris)
Normalisasi Input #
// Bersihkan input: hanya huruf dan angka, lowercase
fun normalisasi(input: String): String =
input.lowercase()
.filter { it.isLetterOrDigit() || it == ' ' }
.trim()
.replace(Regex("\\s+"), " ")
normalisasi(" Halo Dunia!! 123 ") // "halo dunia 123"
// Sensor kata: ganti karakter tengah dengan *
fun sensorKata(kata: String): String {
if (kata.length <= 2) return kata
return kata.first() +
"*".repeat(kata.length - 2) +
kata.last()
}
sensorKata("password") // "p******d"
sensorKata("ab") // "ab"
sensorKata("a") // "a"
// Mask email
fun maskEmail(email: String): String {
val atIndex = email.indexOf('@')
if (atIndex < 0) return email
val lokal = email.substring(0, atIndex)
val domain = email.substring(atIndex)
val terlihat = lokal.take(2)
val mask = "*".repeat(maxOf(0, lokal.length - 2))
return terlihat + mask + domain
}
maskEmail("[email protected]") // "an*********@example.com"
Ringkasan #
CharbukanIntdi Kotlin — keduanya tipe terpisah. Konversi harus eksplisit:.codeuntuk mendapat nilai Unicode,.toChar()untuk konversi Int ke Char.- Fungsi klasifikasi seperti
isLetter(),isDigit(),isUpperCase(),isWhitespace(), danisLetterOrDigit()adalah API utama Char yang menghindari keharusan mengingat rentang kode ASCII.uppercaseChar()danlowercaseChar()(bukantoUpperCase()/toLowerCase()yang deprecated) untuk konversi case. Gunakanjava.util.Locale.ROOTuntuk konversi teknis yang tidak bergantung locale.- Aritmatika Char:
Char + Int = Char,Char - Int = Char,Char - Char = Int. Berguna untuk navigasi alfabet, Caesar cipher, dan konversi digit ke angka.digitToInt()adalah cara idiomatik mengkonversi karakter digit ke nilai numeriknya. Mendukung basis kustom:'F'.digitToInt(16) = 15.- String adalah Iterable — semua operasi collection seperti
filter,map,any,all,groupingBybisa langsung dipakai pada String untuk pemrosesan per karakter.- Validasi input di level karakter lebih efisien dari regex untuk pola sederhana:
nomor.all { it.isDigit() }jauh lebih cepat dari regex\d+.- Emoji dan supplementary characters membutuhkan dua
Char(surrogate pair)..lengthpada String dengan emoji tidak sama dengan jumlah karakter visual — gunakan.codePointCount()untuk hitungan yang akurat.filter { it.isLetter() }danfilter { it.isDigit() }adalah pola idiomatik untuk membersihkan input: hapus semua selain huruf atau selain angka dalam satu baris.- Untuk parser sederhana (CSV, tokenizer, format kustom), iterasi karakter per karakter dengan
whenpadaCharlebih ringan dan lebih mudah dibaca dari regex yang kompleks.