もう迷わない!Kotlinの乱数エラーとトラブルシューティング
乱数の種類
kotlin.random.Random
が生成するのは「擬似乱数(Pseudo-random numbers)」です。これは、完全にランダムに見えますが、実際には初期値(シード)に基づいて決定論的なアルゴリズムによって生成される数値のシーケンスです。同じシードを与えれば、常に同じ乱数のシーケンスが生成されます。
インスタンスの取得
乱数を生成するには、Random
クラスのインスタンスを取得する必要があります。
-
シードを指定したインスタンス
再現性のある乱数シーケンスが必要な場合は、特定のシードを指定してRandom
のインスタンスを作成できます。これは、テストやデバッグなど、常に同じ結果を得たい場合に便利です。import kotlin.random.Random fun main() { val seededRandom = Random(12345) // シード12345でインスタンスを作成 println(seededRandom.nextInt(0, 100)) // 最初の乱数 println(seededRandom.nextInt(0, 100)) // 2番目の乱数 }
上記の場合、
Random(12345)
を複数回実行しても、常に同じ乱数のシーケンスが生成されます。 -
デフォルトのインスタンス
一番簡単な方法は、Random
オブジェクトのコンパニオンオブジェクトが提供するデフォルトのインスタンスを使用することです。このインスタンスは、現在の時刻などの要素に基づいて自動的にシードされます。import kotlin.random.Random fun main() { val randomNumber = Random.nextInt(0, 100) // 0から99までの乱数を生成 println(randomNumber) }
主要な乱数生成メソッド
Random
クラスには、様々な型の乱数を生成するためのメソッドが用意されています。
nextBytes(array: ByteArray)
: 指定されたバイト配列に乱数を書き込みます。nextBoolean()
: trueまたはfalseの乱数を生成します。nextDouble()
: 0.0(含む)から1.0(含まない)までのDouble型の乱数を生成します。範囲指定も可能です。nextLong()
: Long型の整数を生成します。同様に範囲指定も可能です。nextInt()
: 整数を生成します。引数なしの場合、取り得るすべてのInt値の中から乱数を生成します。nextInt(until: Int)
で0からuntil-1
までの範囲、nextInt(from: Int, until: Int)
でfrom
からuntil-1
までの範囲の乱数を生成できます。
使用例
import kotlin.random.Random
fun main() {
// 0から99までのランダムな整数
val num1 = Random.nextInt(100)
println("ランダムな数字 (0-99): $num1")
// 10から20までのランダムな整数
val num2 = Random.nextInt(10, 21)
println("ランダムな数字 (10-20): $num2")
// 0.0から1.0未満のランダムな浮動小数点数
val double1 = Random.nextDouble()
println("ランダムな浮動小数点数 (0.0-1.0): $double1")
// ランダムな真偽値
val boolean1 = Random.nextBoolean()
println("ランダムな真偽値: $boolean1")
// ランダムな要素のシャッフル
val list = mutableListOf("Apple", "Banana", "Cherry")
list.shuffle(Random) // Randomインスタンスを渡すこともできる
println("シャッフルされたリスト: $list")
}
- シードと再現性
シードを指定しない場合、プログラムを実行するたびに異なる乱数シーケンスが得られます。テスト時など、特定の乱数シーケンスを再現したい場合は、シードを指定することを忘れないでください。 - 擬似乱数であること
本当に予測不可能な乱数(暗号学的に安全な乱数)が必要な場合は、java.security.SecureRandom
など、より強力な乱数生成器を使用する必要があります。kotlin.random.Random
は、一般的な用途やゲームなどで十分なランダム性を提供します。
常に同じ乱数が生成される(ランダム性がない)
これはkotlin.random.Random
を使う上で最も頻繁に遭遇する問題の一つです。
原因
- ループ内で毎回インスタンスを作成
ループ処理の中でRandom()
(シードなし)を呼び出して毎回新しいインスタンスを作成している場合。システム時間などに基づいてシードが設定されるため、非常に短い時間で連続してインスタンスを作成すると、同じシードが使われる可能性があります。 - シードの固定
Random(seed: Long)
またはRandom(seed: Int)
のように、毎回同じシード値で新しいRandom
インスタンスを作成している場合。擬似乱数はシード値に基づいて決定論的に生成されるため、同じシードからは常に同じ乱数シーケンスが得られます。
トラブルシューティング
-
インスタンスを再利用する
シードを指定してRandom
インスタンスを作成する場合は、そのインスタンスを使い回すようにします。import kotlin.random.Random fun main() { val myRandom = Random(System.currentTimeMillis()) // 一度だけシードを指定してインスタンスを作成 repeat(5) { println(myRandom.nextInt(100)) // 同じインスタンスから異なる乱数を取得 } }
System.currentTimeMillis()
をシードに使うのは一般的な方法ですが、短い間隔でインスタンスを複数作成すると同じシードになる可能性があるため、注意が必要です。真にランダム性を重視する場合は、やはりRandom.Default
または後述のSecureRandom
が良いでしょう。 -
Random.Defaultを使用する
ほとんどの場合、Random.Default
インスタンスを使用するのが最も簡単で推奨される方法です。これは自動的に良いシードで初期化され、再利用されるため、異なる乱数が生成されます。import kotlin.random.Random fun main() { repeat(5) { println(Random.nextInt(100)) // 毎回異なる乱数が生成される } }
範囲外の乱数が生成される
nextInt()
, nextLong()
, nextDouble()
などの範囲指定で誤解がある場合に発生します。
原因
- fromとuntilの順序間違い
nextInt(from: Int, until: Int)
でfrom
がuntil
以上になっている場合。 - untilの解釈間違い
nextInt(until: Int)
は、0
(含む)からuntil
(含まない)までの乱数を生成します。例えば、nextInt(10)
は0から9までの乱数を生成します。1から10までの乱数を生成したい場合、nextInt(10)
では意図した結果になりません。
トラブルシューティング
-
範囲の確認
- 0からN-1までの乱数
Random.nextInt(N)
- 1からNまでの乱数
Random.nextInt(1, N + 1)
- AからBまでの乱数(AとBを含む)
Random.nextInt(A, B + 1)
import kotlin.random.Random fun main() { // 0から9までの乱数 println(Random.nextInt(10)) // 1から10までの乱数 println(Random.nextInt(1, 11)) // 5から15までの乱数 println(Random.nextInt(5, 16)) }
- 0からN-1までの乱数
Randomのコンストラクタが見つからない、または抽象クラスのエラー
原因
kotlin.random.Random
は抽象クラスであり、直接インスタンスを作成することはできません(Random()
やRandom(seed)
のようなコンストラクタは利用できません)。代わりに、ファクトリ関数やコンパニオンオブジェクトのプロパティを使用します。
トラブルシューティング
- シードを指定してインスタンスを作成する場合
Random(seed)
というファクトリ関数を使用します。import kotlin.random.Random val seededRandom = Random(12345L) // OK
- Random.Defaultを使用する
デフォルトの乱数ジェネレータを使用します。import kotlin.random.Random val randomNumber = Random.nextInt() // OK // val randomInstance = Random() // エラー!
暗号学的に安全な乱数が必要な場合
原因
kotlin.random.Random
は「擬似乱数」であり、予測可能です。セキュリティが重視される場面(例: パスワード生成、暗号鍵生成)では、この擬似乱数では不十分です。
トラブルシューティング
-
java.security.SecureRandomを使用する
Kotlinの標準ライブラリには暗号学的に安全な乱数ジェネレータは含まれていませんが、Javaの標準ライブラリにあるjava.security.SecureRandom
を使用できます。import java.security.SecureRandom fun main() { val secureRandom = SecureRandom() val randomBytes = ByteArray(16) secureRandom.nextBytes(randomBytes) // 暗号学的に安全な乱数バイトを生成 println("Secure Random Bytes: ${randomBytes.joinToString { "%02x".format(it) }}") // SecureRandomを使ってIntを生成する場合 val secureInt = secureRandom.nextInt() println("Secure Random Int: $secureInt") }
SecureRandom
は、より高品質なエントロピーソース(システムのノイズ、OSのイベントなど)を利用するため、初期化に時間がかかる場合があります。
原因
- 必要なパッケージがインポートされていない。
トラブルシューティング
-
コードの冒頭に
import kotlin.random.Random
を追加します。import kotlin.random.Random // これを追加 fun main() { val num = Random.nextInt(100) println(num) }
基本的な乱数生成 (nextInt, nextDouble, nextBoolean)
最も基本的な乱数の生成方法です。引数なしの場合、取りうる全ての範囲から乱数を生成します。
import kotlin.random.Random
fun main() {
println("--- 基本的な乱数生成 ---")
// 任意のInt型の乱数(非常に大きな範囲)
val randomInt = Random.nextInt()
println("任意のInt型の乱数: $randomInt")
// 0.0(含む)から1.0(含まない)までのDouble型の乱数
val randomDouble = Random.nextDouble()
println("0.0から1.0未満のDouble乱数: $randomDouble")
// trueまたはfalseの乱数
val randomBoolean = Random.nextBoolean()
println("ランダムなBoolean: $randomBoolean")
// 任意のLong型の乱数
val randomLong = Random.nextLong()
println("任意のLong型の乱数: $randomLong")
}
特定の範囲の乱数生成 (nextInt(until), nextInt(from, until))
特定の範囲内の整数乱数を生成したい場合に便利です。
nextInt(from: Int, until: Int)
:from
(含む)からuntil
(含まない)までの整数を生成します。nextInt(until: Int)
:0
(含む)からuntil
(含まない)までの整数を生成します。
import kotlin.random.Random
fun main() {
println("\n--- 特定の範囲の乱数生成 ---")
// 0から99までのInt型の乱数(0 <= n < 100)
val randomNumber0To99 = Random.nextInt(100)
println("0から99までの乱数: $randomNumber0To99")
// 1から10までのInt型の乱数(1 <= n <= 10)
// `until`は含まれないので、10を含めるには11を指定する
val randomNumber1To10 = Random.nextInt(1, 11)
println("1から10までの乱数: $randomNumber1To10")
// -5から5までのInt型の乱数(-5 <= n <= 5)
val randomNumberMinus5To5 = Random.nextInt(-5, 6)
println("-5から5までの乱数: $randomNumberMinus5To5")
// 0.0から100.0までのDouble型の乱数(0.0 <= n < 100.0)
val randomDouble0To100 = Random.nextDouble(100.0)
println("0.0から100.0未満のDouble乱数: $randomDouble0To100")
// 50.0から150.0までのDouble型の乱数(50.0 <= n < 150.0)
val randomDouble50To150 = Random.nextDouble(50.0, 150.0)
println("50.0から150.0未満のDouble乱数: $randomDouble50To150")
}
シードを指定した乱数生成(再現性のある乱数)
特定のシード値(初期値)を指定してRandom
インスタンスを作成すると、常に同じ乱数のシーケンスが生成されます。これは、テストやデバッグなど、結果の再現性が必要な場合に非常に役立ちます。
import kotlin.random.Random
fun main() {
println("\n--- シードを指定した乱数生成 ---")
// シード12345でRandomインスタンスを作成
val seededRandom1 = Random(12345L)
println("最初のシーケンス:")
repeat(3) {
println(" ${seededRandom1.nextInt(100)}")
}
// もう一度同じシードでRandomインスタンスを作成
val seededRandom2 = Random(12345L)
println("2番目のシーケンス (同じシード):")
repeat(3) {
println(" ${seededRandom2.nextInt(100)}") // 最初のシーケンスと同じ結果になる
}
// 異なるシードでRandomインスタンスを作成
val seededRandom3 = Random(98765L)
println("異なるシーケンス (異なるシード):")
repeat(3) {
println(" ${seededRandom3.nextInt(100)}")
}
}
コレクションからのランダムな選択
リストや配列などのコレクションからランダムな要素を選択する方法です。
import kotlin.random.Random
fun main() {
println("\n--- コレクションからのランダムな選択 ---")
val fruits = listOf("Apple", "Banana", "Cherry", "Date", "Elderberry")
// リストからランダムな要素を1つ選択
val randomFruit = fruits.random() // Random.Defaultが使用される
println("ランダムなフルーツ: $randomFruit")
// カスタムのRandomインスタンスを使って選択することも可能
val myCustomRandom = Random(System.currentTimeMillis())
val anotherRandomFruit = fruits.random(myCustomRandom)
println("もう一つのランダムなフルーツ (カスタムRandom): $anotherRandomFruit")
}
コレクションのシャッフル
リストの要素をランダムな順序に並び替える方法です。
import kotlin.random.Random
fun main() {
println("\n--- コレクションのシャッフル ---")
val numbers = mutableListOf(1, 2, 3, 4, 5)
println("元のリスト: $numbers")
// リストをシャッフル
numbers.shuffle() // Random.Defaultが使用される
println("シャッフルされたリスト: $numbers")
// カスタムのRandomインスタンスを使ってシャッフル
val cards = mutableListOf("A♠", "K♥", "Q♦", "J♣", "10♠")
val customShuffler = Random(System.currentTimeMillis())
cards.shuffle(customShuffler)
println("シャッフルされたカード: $cards")
}
ランダムバイトの生成 (nextBytes)
暗号化など、低レベルでバイト列の乱数が必要な場合に使用します。
import kotlin.random.Random
fun main() {
println("\n--- ランダムバイトの生成 ---")
// 16バイトのByteArrayを作成
val randomBytes = ByteArray(16)
// バイト配列にランダムな値を書き込む
Random.nextBytes(randomBytes)
println("生成されたランダムバイト:")
// バイト配列を16進数文字列として表示
println(randomBytes.joinToString(separator = "") { "%02x".format(it) })
// 特定のByteArrayに書き込む例
val myBuffer = ByteArray(8)
Random.nextBytes(myBuffer, 0, 4) // インデックス0から3までの4バイトに書き込む
println("部分的に書き込まれたバッファ: ${myBuffer.joinToString { "%02x".format(it) }}")
}
java.util.Random (Java標準ライブラリ)
kotlin.random.Random
が導入される以前は、JVM上で乱数を生成する標準的な方法でした。現在でも、既存のJavaコードベースと連携する必要がある場合や、特定の挙動が必要な場合に利用されます。
特徴
- Kotlinからの相互運用性
KotlinのRandom
インスタンスをasJavaRandom()
拡張関数でjava.util.Random
に変換したり、その逆を行ったりできます。 - スレッドセーフではない
複数のスレッドから同時に同じRandom
インスタンスにアクセスすると、パフォーマンスが低下したり、競合状態が発生する可能性があります。各スレッドが独自のRandom
インスタンスを持つことが推奨されます。 - シード指定
コンストラクタでシードを指定できます(例:java.util.Random(seed)
)。 - 擬似乱数
kotlin.random.Random
と同様に擬似乱数です。
使用例
import java.util.Random // java.util.Random をインポート
fun main() {
println("--- java.util.Random ---")
val javaRandom = Random() // デフォルトのシードでインスタンスを作成
println("Java Random nextInt(): ${javaRandom.nextInt()}")
println("Java Random nextInt(100): ${javaRandom.nextInt(100)}") // 0から99まで
val seededJavaRandom = Random(42L) // シードを指定
println("Seeded Java Random nextInt(): ${seededJavaRandom.nextInt(10)}")
println("Seeded Java Random nextInt(): ${seededJavaRandom.nextInt(10)}")
// KotlinのRandomをJavaのRandomに変換
val kotlinRandom = kotlin.random.Random.Default
val convertedJavaRandom = kotlinRandom.asJavaRandom()
println("Kotlin RandomをJava Randomに変換: ${convertedJavaRandom.nextInt(100)}")
}
java.security.SecureRandom (Java標準ライブラリ)
暗号学的に安全な乱数(CSPRNG: Cryptographically Secure Pseudo-Random Number Generator)が必要な場合に利用されます。予測不可能性が非常に高く、セキュリティが重視される場面(パスワード生成、暗号鍵生成、セッションID生成など)に適しています。
特徴
- スレッドセーフ
基本的にスレッドセーフですが、実装によってはパフォーマンスに影響がある場合があります。 - 初期化に時間がかかる場合がある
エントロピーソースの収集に時間がかかるため、通常のRandom
よりも初期化に時間がかかることがあります。 - 暗号学的に安全
高品質なエントロピーソース(OSのノイズ、ハードウェアイベントなど)からシードを取得し、予測不可能な乱数を生成します。
使用例
import java.security.SecureRandom
fun main() {
println("\n--- java.security.SecureRandom ---")
val secureRandom = SecureRandom() // セキュリティの高い乱数ジェネレーター
val passwordBytes = ByteArray(16)
secureRandom.nextBytes(passwordBytes) // ランダムなバイト列を生成
println("生成されたセキュアなバイト列:")
println(passwordBytes.joinToString("") { "%02x".format(it) })
// 0から99までのセキュアな乱数
val secureInt = secureRandom.nextInt(100)
println("セキュアなInt乱数 (0-99): $secureInt")
}
java.util.concurrent.ThreadLocalRandom (Java標準ライブラリ)
Java 7で導入された、マルチスレッド環境での乱数生成に特化したクラスです。各スレッドが自身の乱数ジェネレーターを持つため、共有のRandom
インスタンスを使用するよりも競合が少なく、パフォーマンスが向上します。
特徴
- 擬似乱数
Random
と同様に擬似乱数です。 - シードの指定不可
シードを明示的に指定することはできません。システム時間などに基づいて自動的にシードされます。 - 高いパフォーマンス
マルチスレッド環境でのパフォーマンスが優れています。 - スレッドローカル
スレッドごとに独立した乱数ジェネレーターを提供します。
使用例
import java.util.concurrent.ThreadLocalRandom
fun main() {
println("\n--- java.util.concurrent.ThreadLocalRandom ---")
// 現在のスレッドのThreadLocalRandomインスタンスを取得
val threadRandom = ThreadLocalRandom.current()
println("ThreadLocalRandom nextInt(): ${threadRandom.nextInt()}")
println("ThreadLocalRandom nextInt(0, 100): ${threadRandom.nextInt(0, 100)}") // 0から99まで
// マルチスレッド環境での使用例(概念)
// 例えば、CallableやRunnableを使って複数のスレッドで乱数を生成する場合
val results = mutableListOf<Int>()
val threads = List(5) {
Thread {
val localRandom = ThreadLocalRandom.current()
synchronized(results) { // resultsへの追加は同期化が必要
results.add(localRandom.nextInt(1000))
}
}
}
threads.forEach { it.start() }
threads.forEach { it.join() }
println("マルチスレッドで生成された乱数の一部: ${results.take(5)}")
}
特定の要件を満たすために、Kotlinのコレクションの拡張関数を活用することもできます。
-
MutableList.shuffle()
リストの要素をランダムな順序に並べ替えます。内部的にはkotlin.random.Random.Default
を使用します。fun main() { println("\n--- コレクションの拡張関数 (shuffle) ---") val numbers = mutableListOf(1, 2, 3, 4, 5) numbers.shuffle() println("シャッフルされた数字: $numbers") }
-
List.random() / Array.random()
コレクションからランダムな要素を一つ選択します。内部的にはkotlin.random.Random.Default
を使用します。fun main() { println("\n--- コレクションの拡張関数 (random) ---") val items = listOf("Rock", "Paper", "Scissors") val randomChoice = items.random() println("ランダムな選択: $randomChoice") }
代替手段 | 用途 | 特徴 |
---|---|---|
kotlin.random.Random | 汎用的な擬似乱数、Kotlinプロジェクトのデフォルト | Kotlinネイティブ、マルチプラットフォーム対応、デフォルトインスタンスは自動シード、シード指定で再現可能、スレッドセーフではない(推奨されないが、Random.Default は内部的にマルチスレッド対応のメカニズムを持つ)。 |
java.util.Random | 既存のJavaコードとの連携、特定のシード管理が必要な場合 | JVM環境のみ、擬似乱数、シード指定可能、スレッドセーフではないため各スレッドでインスタンスを持つ推奨。 |
java.security.SecureRandom | 高いセキュリティが要求される場合(パスワード、鍵生成など) | JVM環境のみ、暗号学的に安全な乱数、予測不可能、初期化に時間がかかる場合がある、スレッドセーフ。 |
java.util.concurrent.ThreadLocalRandom | マルチスレッド環境でのパフォーマンス重視 | JVM環境のみ、スレッドローカルで高いパフォーマンス、シード指定不可、擬似乱数。 |
コレクションの拡張関数 | リストからのランダム選択、リストのシャッフル | kotlin.random.Random.Default を内部的に利用、簡潔な記述。 |