Random #
Angka acak adalah kebutuhan yang lebih umum dari yang terpikirkan — membuat data dummy untuk testing, mengacak urutan tampilan, memilih item promosi secara acak, simulasi Monte Carlo, game mechanics, token keamanan, dan banyak lagi. Kotlin hadir dengan kotlin.random.Random yang merupakan API modern dan multiplatform — berbeda dari java.util.Random yang hanya tersedia di JVM. kotlin.random.Random bekerja di Kotlin/JVM, Kotlin/JS, dan Kotlin/Native tanpa perlu perubahan kode. Artikel ini membahas seluruh API Random Kotlin: dari penggunaan dasar, kontrol reproducibility dengan seed, pengacakan collection, pengambilan sampel, hingga pola idiomatik untuk pengujian dan simulasi.
kotlin.random.Random vs java.util.Random #
flowchart LR
A["Butuh angka acak\ndi Kotlin"] --> B{Platform?}
B --> C["JVM only\njava.util.Random\njava.util.ThreadLocalRandom\njava.security.SecureRandom"]
B --> D["Multiplatform\nkotlin.random.Random\nBerjalan di JVM, JS, Native"]
D --> E["Recommended\nuntuk kode baru"]
C --> F["Gunakan hanya jika\nbutuh API Java spesifik\natau SecureRandom"]// Java way — hanya JVM
import java.util.Random
val javaRandom = Random()
val n1 = javaRandom.nextInt(100)
// Kotlin way — multiplatform, API lebih bersih
import kotlin.random.Random
val n2 = Random.nextInt(100) // 0 sampai 99 (eksklusif)
val n3 = Random.nextInt(1, 101) // 1 sampai 100 (inklusif)
val d = Random.nextDouble() // 0.0 sampai kurang dari 1.0
val b = Random.nextBoolean() // true atau false
// Random.Default adalah instance global yang thread-safe
// Bisa digunakan langsung tanpa membuat instance baru
println(Random.nextInt(10)) // 0..9
// Atau buat instance sendiri
val rng = Random(seed = 42)
println(rng.nextInt(10))
Fungsi Dasar #
nextInt — Bilangan Bulat Acak #
// nextInt() tanpa argumen — seluruh range Int
val acakPenuh: Int = Random.nextInt()
// nextInt(bound) — 0 sampai bound-1 (eksklusif atas)
val nol_sampai_9: Int = Random.nextInt(10) // 0, 1, 2, ..., 9
val nol_sampai_99: Int = Random.nextInt(100) // 0, 1, 2, ..., 99
// nextInt(from, until) — range eksplisit
val satu_sampai_6: Int = Random.nextInt(1, 7) // dadu: 1, 2, 3, 4, 5, 6
val negatif: Int = Random.nextInt(-10, 10) // -10 sampai 9
// Gunakan IntRange
val dariRange: Int = Random.nextInt(1..6) // lebih ekspresif
val dariRange2: Int = (1..6).random() // shortcut pada range
// ANTI-PATTERN: implementasi manual yang rawan off-by-one
val salah = (Math.random() * 6).toInt() + 1 // bergantung java.lang.Math
// BENAR: gunakan nextInt dengan range yang jelas
val dadu = Random.nextInt(1, 7) // 1 sampai 6 inklusif
nextDouble dan nextFloat #
// nextDouble() — 0.0 (inklusif) sampai 1.0 (eksklusif)
val prob: Double = Random.nextDouble() // distribusi uniform [0.0, 1.0)
// nextDouble(from, until) — range kustom
val suhu: Double = Random.nextDouble(36.0, 38.0) // 36.0 sampai kurang dari 38.0
val lon: Double = Random.nextDouble(95.0, 141.0) // longitude Indonesia
val lat: Double = Random.nextDouble(-11.0, 6.0) // latitude Indonesia
// nextFloat — presisi lebih rendah, cocok untuk grafis
val f: Float = Random.nextFloat() // [0.0f, 1.0f)
// Pembangkitan dalam distribusi tertentu
// Distribusi normal (Gaussian) — tidak ada di stdlib, tapi mudah dibuat
fun nextGaussian(mean: Double = 0.0, std: Double = 1.0): Double {
// Box-Muller transform
val u1 = Random.nextDouble()
val u2 = Random.nextDouble()
val z0 = kotlin.math.sqrt(-2.0 * kotlin.math.ln(u1)) *
kotlin.math.cos(2.0 * kotlin.math.PI * u2)
return mean + std * z0
}
// Distribusi eksponensial
fun nextExponential(lambda: Double = 1.0): Double =
-kotlin.math.ln(1.0 - Random.nextDouble()) / lambda
nextBoolean dan nextBits #
// nextBoolean — 50/50 true/false
val koinLempar: Boolean = Random.nextBoolean() // true atau false, peluang sama
// Peluang tidak 50/50
fun acakDenganPeluang(peluang: Double): Boolean {
require(peluang in 0.0..1.0) { "Peluang harus 0.0 sampai 1.0" }
return Random.nextDouble() < peluang
}
val menang = acakDenganPeluang(0.30) // 30% kemungkinan true
val hujan = acakDenganPeluang(0.70) // 70% kemungkinan true
// nextBits — ambil n bit acak (berguna untuk operasi bit level)
val bits: Int = Random.nextBits(8) // nilai 0-255
// nextLong — bilangan Long acak
val id: Long = Random.nextLong()
val idRange: Long = Random.nextLong(1_000_000L, 9_999_999L) // ID 7 digit
nextBytes — Array Byte Acak #
// nextBytes — isi array dengan byte acak
val buffer = ByteArray(16)
Random.nextBytes(buffer)
println(buffer.toHexString()) // hex dump dari 16 byte acak
// Atau langsung buat array baru
val tokenBytes: ByteArray = Random.nextBytes(32) // 256 bit untuk token
// Konversi ke hex string
fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
// Generate token sederhana (BUKAN untuk keamanan produksi — gunakan SecureRandom)
fun buatToken(panjangByte: Int = 16): String =
Random.nextBytes(panjangByte).toHexString()
println(buatToken()) // "a3f8c2d1e7b4..." (32 karakter hex dari 16 byte)
kotlin.random.Randomtidak cocok untuk keperluan keamanan seperti token autentikasi, password reset, atau kriptografi. Untuk keamanan, gunakanjava.security.SecureRandomdi JVM.kotlin.random.Randommenggunakan PRNG (Pseudo-Random Number Generator) yang deterministik dan bisa diprediksi jika seed diketahui.
Seed — Reproducibility #
Seed adalah nilai awal yang menentukan seluruh urutan angka acak yang akan dihasilkan. Instance Random dengan seed yang sama selalu menghasilkan urutan yang sama — ini sangat berguna untuk testing, debugging, dan simulasi yang perlu direplikasi.
// Tanpa seed — berbeda setiap run
val r1 = Random
println(r1.nextInt(100)) // hasil berbeda setiap run
// Dengan seed — sama setiap run
val seeded = Random(seed = 42)
println(seeded.nextInt(100)) // selalu menghasilkan nilai yang sama
println(seeded.nextInt(100)) // nilai berikutnya juga deterministik
// Dua instance dengan seed yang sama menghasilkan urutan yang identik
val a = Random(42)
val b = Random(42)
val listA = List(5) { a.nextInt(100) }
val listB = List(5) { b.nextInt(100) }
println(listA == listB) // true — persis sama
// PENTING: seed membuat URUTAN deterministik, bukan satu nilai saja
val c = Random(42)
repeat(3) { println(c.nextInt(100)) }
// Output selalu sama: misal 33, 1, 76
// Aplikasi seed untuk testing
class GameSimulasi(private val rng: Random = Random.Default) {
fun lemparDadu(): Int = rng.nextInt(1, 7)
fun acakPosisi(): Pair<Int, Int> = rng.nextInt(0, 10) to rng.nextInt(0, 10)
}
// Dalam test — seed untuk reproduksi
@Test
fun `simulasi harus deterministik dengan seed`() {
val rngTetap = Random(seed = 12345)
val game = GameSimulasi(rngTetap)
val hasilDadu = game.lemparDadu()
val posisi = game.acakPosisi()
// Dengan seed yang sama, hasil selalu sama — test tidak flaky
assertEquals(hasilDadu, GameSimulasi(Random(12345)).lemparDadu())
}
Pengacakan Collection #
shuffle — Acak Urutan #
// shuffle — in-place, hanya MutableList
val kartu = (1..52).toMutableList()
kartu.shuffle() // urutan acak in-place
kartu.shuffle(Random(42)) // dengan seed untuk reproducibility
// shuffled — menghasilkan List baru (tidak mengubah aslinya)
val daftar = listOf("A", "B", "C", "D", "E")
val teracak: List<String> = daftar.shuffled()
val teracakSeed: List<String> = daftar.shuffled(Random(99))
println(daftar) // [A, B, C, D, E] — tidak berubah
println(teracak) // [C, A, E, B, D] — urutan acak
random — Ambil Elemen Acak #
// random() — ambil satu elemen secara acak
val buah = listOf("apel", "jeruk", "mangga", "durian", "jambu")
val acak: String = buah.random()
val acakSeed: String = buah.random(Random(42))
// randomOrNull — aman untuk list kosong
val kosong = emptyList<String>()
val hasilNull: String? = kosong.randomOrNull() // null, tidak throw
val hasilNormal: String? = buah.randomOrNull() // salah satu buah
// ANTI-PATTERN: akses index acak manual
val salah = buah[Random.nextInt(buah.size)] // bisa throw jika list kosong
// BENAR: gunakan .random() atau .randomOrNull()
val benar = buah.randomOrNull() ?: "tidak ada buah"
// random() pada Range
val angkaAcak = (1..100).random() // angka antara 1 dan 100
val hurufAcak = ('a'..'z').random() // huruf kecil acak
Sampling — Ambil Beberapa Elemen #
// Kotlin tidak punya fungsi sampling bawaan — tapi mudah dibuat
// Sampling tanpa pengembalian (setiap elemen hanya bisa dipilih sekali)
fun <T> List<T>.sampel(n: Int, rng: Random = Random): List<T> {
require(n <= size) { "Tidak bisa ambil $n sampel dari list berukuran $size" }
return shuffled(rng).take(n)
}
val peserta = listOf("Andi", "Budi", "Clara", "Dina", "Eva", "Fani", "Gani")
val pemenang3 = peserta.sampel(3)
println(pemenang3) // 3 nama acak tanpa duplikat
// Sampling dengan pengembalian (elemen bisa dipilih lebih dari sekali)
fun <T> List<T>.sampelDenganPengembalian(n: Int, rng: Random = Random): List<T> =
List(n) { random(rng) }
val dadux5 = (1..6).toList().sampelDenganPengembalian(5)
// Misal: [3, 6, 3, 1, 5] — angka bisa muncul lebih dari sekali
// Weighted random — pilih berdasarkan bobot
fun <T> pilihBerbobot(pilihan: List<Pair<T, Double>>, rng: Random = Random): T {
val totalBobot = pilihan.sumOf { it.second }
val acak = rng.nextDouble() * totalBobot
var kumulatif = 0.0
for ((item, bobot) in pilihan) {
kumulatif += bobot
if (acak < kumulatif) return item
}
return pilihan.last().first
}
val loot = listOf(
"Pedang Biasa" to 0.60,
"Pedang Langka" to 0.25,
"Pedang Epik" to 0.10,
"Pedang Legendaris" to 0.05
)
val item = pilihBerbobot(loot) // 60% dapat Pedang Biasa, 5% dapat Legendaris
Pembangkitan Data untuk Testing #
Salah satu penggunaan Random paling bernilai adalah membuat data dummy yang realistis untuk testing.
// Data generator yang deterministic dengan seed
class DataGenerator(seed: Long = System.currentTimeMillis()) {
private val rng = Random(seed)
private val namaDepan = listOf("Andi", "Budi", "Clara", "Dina", "Eva",
"Fani", "Gani", "Hana", "Indra", "Joko")
private val namaBelakang = listOf("Santoso", "Wijaya", "Kusuma", "Pratama",
"Hidayat", "Nugroho", "Saputra", "Utama")
private val departemen = listOf("Engineering", "Marketing", "HR", "Finance", "Operations")
private val kota = listOf("Jakarta", "Bandung", "Surabaya", "Medan", "Makassar")
fun nama(): String =
"${namaDepan.random(rng)} ${namaBelakang.random(rng)}"
fun email(nama: String): String =
"${nama.lowercase().replace(" ", ".")}${rng.nextInt(100)}@example.com"
fun gaji(): Double =
rng.nextDouble(8_000_000.0, 25_000_000.0).let {
(it / 500_000).toLong() * 500_000.0 // bulatkan ke 500rb
}
fun usia(): Int = rng.nextInt(22, 58)
fun karyawan(): Map<String, Any> {
val nama = nama()
return mapOf(
"nama" to nama,
"email" to email(nama),
"departemen" to departemen.random(rng),
"gaji" to gaji(),
"usia" to usia(),
"kota" to kota.random(rng)
)
}
fun daftarKaryawan(n: Int): List<Map<String, Any>> =
List(n) { karyawan() }
}
// Penggunaan di test — seed sama menghasilkan data sama
val gen = DataGenerator(seed = 42L)
val karyawan100 = gen.daftarKaryawan(100)
// Penggunaan di development — seed acak untuk variasi
val genDev = DataGenerator()
val dataDev = genDev.daftarKaryawan(50)
Simulasi dengan Random #
// Simulasi Monte Carlo — estimasi nilai Pi
fun estimasiPi(iterasi: Int, seed: Long = 42L): Double {
val rng = Random(seed)
var dalamLingkaran = 0
repeat(iterasi) {
val x = rng.nextDouble(-1.0, 1.0)
val y = rng.nextDouble(-1.0, 1.0)
if (x * x + y * y <= 1.0) dalamLingkaran++
}
return 4.0 * dalamLingkaran / iterasi
}
println(estimasiPi(1_000)) // ~3.14 (kasar)
println(estimasiPi(1_000_000)) // ~3.14159 (lebih akurat)
println(estimasiPi(10_000_000)) // ~3.141592 (sangat akurat)
// Simulasi random walk
data class Posisi(val x: Int, val y: Int)
fun randomWalk(langkah: Int, seed: Long = 42L): List<Posisi> {
val rng = Random(seed)
val arah = listOf(
Posisi(0, 1), // atas
Posisi(0, -1), // bawah
Posisi(1, 0), // kanan
Posisi(-1, 0) // kiri
)
val jejak = mutableListOf(Posisi(0, 0))
repeat(langkah) {
val sekarang = jejak.last()
val gerak = arah.random(rng)
jejak.add(Posisi(sekarang.x + gerak.x, sekarang.y + gerak.y))
}
return jejak
}
val jejak = randomWalk(100)
println("Posisi akhir: ${jejak.last()}")
UUID dan ID Unik #
// UUID menggunakan java.util.UUID (JVM only)
import java.util.UUID
val uuid = UUID.randomUUID().toString()
// "550e8400-e29b-41d4-a716-446655440000"
// ID numerik unik dengan timestamp + random
fun buatIdUnik(): Long {
val timestamp = System.currentTimeMillis()
val random = Random.nextLong(0, 999_999)
return timestamp * 1_000_000 + random
}
// Kode referensi yang mudah dibaca manusia
fun buatKodeReferensi(panjang: Int = 8): String {
val karakter = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" // tanpa karakter ambigu
return buildString {
repeat(panjang) { append(karakter.random()) }
}
}
println(buatKodeReferensi()) // misal: "K7MPQR2X"
println(buatKodeReferensi(12)) // misal: "3HMKPQ7RVXZT"
// Kode OTP (One-Time Password) 6 digit
fun buatOTP(): String = Random.nextInt(100_000, 999_999).toString()
println(buatOTP()) // "847392"
Pola Idiomatik #
Dependency Injection untuk Testability #
// ANTI-PATTERN: Random.Default hard-coded — susah di-test
class RekomendatorProduk(val katalog: List<Produk>) {
fun rekomendasikan(): Produk = katalog.random() // tidak bisa dikontrol di test
}
// BENAR: inject Random sebagai parameter
class RekomendatorProduk(
val katalog: List<Produk>,
private val rng: Random = Random.Default
) {
fun rekomendasikan(): Produk = katalog.random(rng)
fun rekomendasikan(n: Int): List<Produk> = katalog.shuffled(rng).take(n)
}
// Test dengan seed deterministik
@Test
fun `rekomendasi harus konsisten dengan seed`() {
val katalog = listOf(Produk("A", 1.0, 1), Produk("B", 2.0, 1), Produk("C", 3.0, 1))
val reko = RekomendatorProduk(katalog, Random(42))
val hasil1 = reko.rekomendasikan()
val rekoSama = RekomendatorProduk(katalog, Random(42))
val hasil2 = rekoSama.rekomendasikan()
assertEquals(hasil1, hasil2) // selalu sama dengan seed yang sama
}
Exponential Backoff dengan Jitter #
// Retry dengan exponential backoff + random jitter
// Jitter mencegah thundering herd — semua client retry bersamaan
fun hitungJeda(percobaan: Int, jedaBase: Long = 1000L, maxJeda: Long = 30_000L): Long {
val eksponensial = jedaBase * (1L shl percobaan.coerceAtMost(10))
val jitter = Random.nextLong(0, eksponensial / 2) // ±50% jitter
return (eksponensial + jitter).coerceAtMost(maxJeda)
}
// Percobaan 0: ~1000ms + jitter 0-500ms
// Percobaan 1: ~2000ms + jitter 0-1000ms
// Percobaan 2: ~4000ms + jitter 0-2000ms
// ...
suspend fun retryDenganJitter(
maxPercobaan: Int = 3,
aksi: suspend () -> Unit
) {
repeat(maxPercobaan) { percobaan ->
try {
aksi()
return
} catch (e: Exception) {
if (percobaan == maxPercobaan - 1) throw e
val jeda = hitungJeda(percobaan)
kotlinx.coroutines.delay(jeda)
}
}
}
Ringkasan #
kotlin.random.Randomadalah pilihan utama untuk kode Kotlin — multiplatform (JVM, JS, Native), thread-safe, dan API lebih bersih darijava.util.Random.Random.nextInt(from, until)untuk integer acak dalam range.(1..6).random()adalah shortcut idiomatik pada range langsung.Random.nextDouble()menghasilkan nilai uniform [0.0, 1.0). Untuk range kustom gunakanRandom.nextDouble(from, until).- Seed membuat urutan angka acak deterministik — instance dengan seed yang sama menghasilkan urutan yang identik. Gunakan seed untuk test yang reproducible dan simulasi yang bisa direplikasi.
shuffled()menghasilkan List baru dengan urutan acak tanpa mengubah original.shuffle()mengacak in-place dan hanya bisa dipanggil padaMutableList..random()untuk mengambil satu elemen acak dari collection..randomOrNull()untuk penanganan aman pada collection yang mungkin kosong.- Weighted random tidak tersedia bawaan — implementasikan dengan menghitung total bobot, generate angka acak dalam range total, lalu pilih item berdasarkan bobot kumulatif.
- Inject
Randomsebagai dependency agar kode yang menggunakan random mudah di-test — passRandom(seed)saat testing,Random.Defaultuntuk produksi.kotlin.random.Randomtidak aman untuk kriptografi — gunakanjava.security.SecureRandomuntuk token keamanan, session ID, dan keperluan kriptografi lainnya.- Exponential backoff dengan jitter adalah pola penting untuk retry logic di sistem terdistribusi — jitter mencegah semua client melakukan retry secara bersamaan (thundering herd).