Characters

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 #

FungsiDeskripsiContoh trueContoh 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 #

  • Char bukan Int di Kotlin — keduanya tipe terpisah. Konversi harus eksplisit: .code untuk mendapat nilai Unicode, .toChar() untuk konversi Int ke Char.
  • Fungsi klasifikasi seperti isLetter(), isDigit(), isUpperCase(), isWhitespace(), dan isLetterOrDigit() adalah API utama Char yang menghindari keharusan mengingat rentang kode ASCII.
  • uppercaseChar() dan lowercaseChar() (bukan toUpperCase()/toLowerCase() yang deprecated) untuk konversi case. Gunakan java.util.Locale.ROOT untuk 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, groupingBy bisa 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). .length pada String dengan emoji tidak sama dengan jumlah karakter visual — gunakan .codePointCount() untuk hitungan yang akurat.
  • filter { it.isLetter() } dan filter { 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 when pada Char lebih ringan dan lebih mudah dibaca dari regex yang kompleks.

← Sebelumnya: Numbers   Berikutnya: Sequences →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact