Angka yang sama dapat ditampilkan dengan sangat berbeda tergantung negara. 1,234,567.89 di Amerika Serikat sama maknanya dengan 1.234.567,89 di Indonesia atau Jerman, dan dengan 1 234 567,89 di Prancis. Perbedaannya bukan sekadar estetika — pemisah grup dan simbol desimal yang salah dapat menyebabkan parsing gagal atau salah interpretasi. Kotlin/JVM mewarisi kelas java.util.Locale dan java.text.NumberFormat dari Java untuk menangani perbedaan ini. Chapter ini membongkar bagaimana Locale bekerja dan bagaimana NumberFormat menggunakannya untuk memformat angka, mata uang, dan persentase.
Apa itu Locale #
Locale adalah identifier yang merepresentasikan kombinasi bahasa dan negara (atau wilayah). Konstruksi paling umum:
import java.util.Locale
val us = Locale.US // Locale konstan
val de = Locale.GERMANY // Locale konstan
val id = Locale("id", "ID") // Bahasa Indonesia, negara Indonesia
val fr = Locale.FRANCE // Locale konstan
val jp = Locale.JAPAN // Locale konstanLocale memengaruhi banyak aspek: pemisah grup, simbol desimal, nama hari/bulan, simbol mata uang, bahkan urutan penulisan tanggal. Method String.format() menerima Locale sebagai argumen pertama untuk memengaruhi outputnya.
Format Angka dengan Locale #
import java.util.Locale
import java.text.NumberFormat
fun main() {
val angka = 1234567.89
println("Locale default: ${"%,.2f".format(angka)}")
println("Locale US: ${"%,.2f".format(Locale.US, angka)}")
println("Locale German: ${"%,.2f".format(Locale.GERMANY, angka)}")
println("Locale ID: ${"%,.2f".format(Locale("id", "ID"), angka)}")
println("Locale France: ${"%,.2f".format(Locale.FRANCE, angka)}")
println("Locale Japan: ${"%,.2f".format(Locale.JAPAN, angka)}")
println("--- NumberFormat.getInstance ---")
val nfUS = NumberFormat.getInstance(Locale.US)
val nfDE = NumberFormat.getInstance(Locale.GERMANY)
val nfID = NumberFormat.getInstance(Locale("id", "ID"))
println("US: ${nfUS.format(angka)}")
println("DE: ${nfDE.format(angka)}")
println("ID: ${nfID.format(angka)}")
println("--- Currency ---")
val cfUS = NumberFormat.getCurrencyInstance(Locale.US)
val cfID = NumberFormat.getCurrencyInstance(Locale("id", "ID"))
val cfJP = NumberFormat.getCurrencyInstance(Locale.JAPAN)
println("US: ${cfUS.format(angka)}")
println("ID: ${cfID.format(angka)}")
println("JP: ${cfJP.format(angka)}")
println("--- Integer ---")
val intUS = NumberFormat.getIntegerInstance(Locale.US)
val intDE = NumberFormat.getIntegerInstance(Locale.GERMANY)
println("US: ${intUS.format(angka)}")
println("DE: ${intDE.format(angka)}")
println("--- Persen ---")
val persen = 0.875
val pfUS = NumberFormat.getPercentInstance(Locale.US)
val pfDE = NumberFormat.getPercentInstance(Locale.GERMANY)
println("US: ${pfUS.format(persen)}")
println("DE: ${pfDE.format(persen)}")
}Output yang Dihasilkan:
Locale default: 1,234,567.89
Locale US: 1,234,567.89
Locale German: 1.234.567,89
Locale ID: 1.234.567,89
Locale France: 1 234 567,89
Locale Japan: 1,234,567.89
--- NumberFormat.getInstance ---
US: 1,234,567.89
DE: 1.234.567,89
ID: 1.234.567,89
--- Currency ---
US: $1,234,567.89
ID: Rp1.234.567,89
JP: ¥1,234,568
--- Integer ---
US: 1,234,568
DE: 1.234.568
--- Persen ---
US: 88%
DE: 88 %Mari kita telaah baris-baris kode di atas:
- Baris 8 (
"%,.2f".format(angka)tanpa Locale): pemanggilan extension.format()tanpa argumen Locale menggunakan Locale default JVM. Di sebagian besar environment server, default adalahLocale.US, sehingga output1,234,567.89. Namun default ini bisa berubah — jangan andalkan untuk output yang harus konsisten lintas mesin. - Baris 9 (
"%,.2f".format(Locale.US, angka)): pemanggilan dengan Locale eksplisit. Output dijamin1,234,567.89di mesin mana pun. Specifier,memicu pemisah grup sesuai Locale. - Baris 10 (
Locale.GERMANY): Jerman menggunakan titik sebagai pemisah grup dan koma sebagai simbol desimal. Output:1.234.567,89. - Baris 11 (
Locale("id", "ID")): Indonesia juga menggunakan titik untuk pemisah grup dan koma untuk desimal, sama seperti Jerman. KonstruktorLocale(language, country)menerima kode bahasa ISO 639 (id) dan kode negara ISO 3166 (ID). - Baris 12 (
Locale.FRANCE): Prancis menggunakan spasi non-breaking (U+00A0) sebagai pemisah grup dan koma untuk desimal. Output:1 234 567,89— di terminal, spasi ini terlihat seperti spasi biasa. - Baris 13 (
Locale.JAPAN): Jepang menggunakan format yang sama dengan US (koma untuk grup, titik untuk desimal) untuk angka biasa, tetapi menggunakan simbol mata uang¥yang berbeda. - Baris 16–20 (
NumberFormat.getInstance(locale)):NumberFormatadalah kelas abstrak yang memformat angka sesuai aturan Locale. MethodgetInstancemengembalikan formatter default untuk angka umum. Kita panggil method.format(angka)untuk mendapatkan string. - Baris 23–28 (
getCurrencyInstance): khusus untuk mata uang. Method ini menambahkan simbol mata uang di depan dan menggunakan aturan penulisan mata uang negara tersebut. Untuk Jepang,¥1,234,568— perhatikan tidak ada desimal karena Yen secara tradisional tidak punya pecahan sen. - Baris 31–34 (
getIntegerInstance): memformat sebagai bilangan bulat — desimal dibulatkan ke bilangan bulat terdekat. Pemisah grup tetap berlaku sesuai Locale. - Baris 38–40 (
getPercentInstance): memformat nilai desimal sebagai persentase. Nilai0.875dikalikan100secara otomatis menjadi88%. Perhatikan di Jerman ada spasi sebelum tanda persen (88 %) — konvensi yang diikuti oleh beberapa Locale Eropa.
Perbandingan Format Lintas Locale #
Tabel berikut merangkum bagaimana angka 1234567.89 ditampilkan di berbagai Locale:
| Locale | Pemisah grup | Desimal | Contoh |
|---|---|---|---|
Locale.US | Koma , | Titik . | 1,234,567.89 |
Locale.GERMANY | Titik . | Koma , | 1.234.567,89 |
Locale("id", "ID") | Titik . | Koma , | 1.234.567,89 |
Locale.FRANCE | Spasi NBSP | Koma , | 1 234 567,89 |
Locale.JAPAN | Koma , | Titik . | 1,234,567.89 |
Locale.ITALY | Titik . | Koma , | 1.234.567,89 |
Format Mata Uang #
NumberFormat.getCurrencyInstance(locale) adalah cara standar memformat nilai uang. Method ini menambahkan simbol mata uang dan menggunakan jumlah desimal yang sesuai untuk mata uang tersebut (Yen = 0, Dolar = 2, Dinar Bahrain = 3):
val cfID = NumberFormat.getCurrencyInstance(Locale("id", "ID"))
println(cfID.format(1500000.0)) // Rp1.500.000,00Untuk format mata uang yang lebih kompleks (misalnya menampilkan kode ISO IDR alih-alih simbol Rp), gunakan DecimalFormat dengan pattern kustom — topik yang lebih lanjut dan berada di luar cakupan chapter ini.
Locale Default JVM #
Locale default JVM ditentukan oleh environment variabel sistem operasi (LANG, LC_ALL di Linux; pengaturan Region di Windows). Kita dapat memeriksa dan mengubah Locale default secara terprogram:
println("Default: ${Locale.getDefault()}")
Locale.setDefault(Locale.US)
println("Setelah set: ${Locale.getDefault()}")Referensi Lanjutan #
- Java SE 21 Locale
— dokumentasi
java.util.LocaleJava SE 21 untuk pemisah grup, simbol desimal, dan mata uang
Dengan pemahaman tentang Locale dan NumberFormat, kita siap beranjak ke chapter terakhir section ini: console application fundamentals — bagaimana stdin/stdout/stderr bekerja, exit code, ANSI color, dan perilaku program ketika di-pipe.