Javaの乱数生成を完全解説|Math.random・Random・SecureRandomの違いと使い分け

目次

1. この記事で分かること

Javaで「乱数」を扱おうとすると、Math.random()RandomSecureRandom など複数の方法が出てきて、
「結局どれを使えばいいのか分からない」
と感じる方は多いはずです。

このセクションでは、まず結論として、この記事を最後まで読むことで何ができるようになるのかを整理します。細かい仕組みやコードの前に、全体像を掴んでおくことで、後半の理解が一気に楽になります。

1.1 Javaで乱数を生成する代表的な方法が分かる

この記事では、Javaで乱数を生成する代表的な手段を段階的に解説します。

具体的には、

  • すぐに使えるシンプルな方法
  • プログラムらしい制御ができる方法
  • セキュリティを意識すべき場面での方法

といったように、用途別に整理して説明します。

そのため、

  • サンプルコードを試したい初心者の方
  • 「とりあえず動けばいい」状態から一歩進みたい方

どちらにも対応できる構成になっています。

1.2 「範囲指定」の考え方とよくある勘違いが理解できる

乱数を扱うときに、多くの人がつまずくのが範囲指定です。

例えば、

  • 0〜9 の乱数を作りたい
  • 1〜6 のサイコロを作りたい
  • マイナス値を含む乱数を作りたい

こうしたケースで、

  • 上限は含まれるのか?
  • 下限は必ず含まれるのか?
  • なぜ思った値が出ないのか?

といった疑問が頻繁に出てきます。

この記事では、
「なぜそうなるのか」→「どう書けば正しくなるのか」
という流れで、初心者にも分かるように解説していきます。

1.3 乱数の「再現性」と「危険性」の違いが分かる

乱数には、

  • 毎回違う結果が出てほしい場合
  • 同じ結果を何度も再現したい場合

という、正反対の要件が存在します。

例えば、

  • テストやデバッグでは「同じ乱数」を再現したい
  • パスワードやトークンでは「予測できない乱数」が必要

この違いを理解せずに使うと、

  • テストが不安定になる
  • セキュリティ的に危険な実装になる

といった問題が起きます。

本記事では、
「再現性が必要な乱数」と「再現してはいけない乱数」
を明確に切り分けて説明します。

1.4 「安全な乱数」と「危険な乱数」を使い分けられるようになる

Javaには、見た目は似ていても用途がまったく異なる乱数生成手段があります。

  • ゲームやサンプルコード向きの乱数
  • 業務アプリでも問題ない乱数
  • セキュリティ用途では絶対に必要な乱数

これらを区別せずに使ってしまうと、

  • 「一見動くが、実は危険」
  • 「将来問題になるコード」

になりがちです。

この記事では、
「この用途ならこれを使う」
という判断基準を、理由と一緒に説明します。

1.5 初心者でも「なぜその書き方なのか」を説明できるようになる

単にコード例を並べるだけではなく、

  • なぜこのメソッドを使うのか
  • なぜこの書き方になるのか
  • なぜ別の方法ではダメなのか

といった背景や考え方を重視して解説します。

そのため、

  • 丸暗記ではなく理解して使いたい方
  • 人に説明できるレベルを目指したい方

にも役立つ内容になっています。

2. Javaの「乱数」とは何か

このセクションでは、Javaで乱数を扱う前に必ず知っておくべき基礎知識を整理します。
ここを飛ばしてコードだけ真似すると、後で必ず混乱します。

2.1 乱数=完全にランダム、ではない

多くの初心者が最初に誤解しやすい点として、
Javaで生成される乱数は「完全にランダム」ではない
という事実があります。

Javaで一般的に使われる乱数は、正確には次のようなものです。

  • 一定のルール(アルゴリズム)に従って計算される数値
  • 見た目はランダムだが、内部的には規則がある

このような乱数は、**擬似乱数(ぎじらんすう)**と呼ばれます。

2.2 擬似乱数(PRNG)とは何か

擬似乱数とは、

  • 初期値(シード)をもとに
  • 数学的な計算を繰り返して
  • それっぽくランダムな数列を作る仕組み

のことです。

特徴としては、

  • 高速に生成できる
  • 同じシードを使えば、同じ乱数列を再現できる
  • コンピュータ上で扱いやすい

という利点があります。

一方で、

  • アルゴリズムを知っていれば予測可能
  • セキュリティ用途には向かない場合がある

という欠点も持っています。

2.3 「再現できる乱数」が便利な場面

一見すると、

同じ乱数が出るのはランダムじゃないからダメでは?

と思われがちですが、実は再現できる乱数は非常に重要です。

例えば、

  • テストコードで毎回同じ結果を確認したい
  • バグ報告を再現したい
  • シミュレーション結果を比較したい

こうした場面では、

  • 実行するたびに結果が変わる
    よりも
  • 毎回同じ乱数が出る

ほうが、圧倒的に扱いやすくなります。

Javaの多くの乱数クラスは、この「再現性」を前提に設計されています。

2.4 逆に「再現できてはいけない」乱数もある

一方で、再現できてはいけない乱数も存在します。

代表的な例は次のようなものです。

  • パスワード生成
  • 認証トークン
  • セッションID
  • ワンタイムキー

これらは、

  • 予測される
  • 再現される

だけで、重大なセキュリティ事故につながります。

そのため、Javaでは通常の擬似乱数とは別に、
セキュリティ用途専用の乱数生成手段が用意されています。

この違いを理解せずに実装すると、

  • 動作はしているが危険なコード
  • レビューや監査で問題になるコード

になりやすい点は、必ず押さえておきましょう。

2.5 「一様分布」とは何か(偏りの基礎)

乱数の話でよく出てくる言葉に、一様分布があります。

一様分布とは、

  • どの値も同じ確率で出現する

という性質のことです。

例えば、

  • サイコロの 1〜6 がすべて同じ確率
  • 0〜9 の数字が均等に出る

この状態が、一様分布です。

Javaの乱数APIは、基本的に一様分布を前提として設計されています。

2.6 初心者がやりがちな「偏った乱数」の例

乱数を自分で加工すると、意図せず偏りが生じることがあります。

よくある例として、

  • %(剰余演算)で無理に範囲を絞る
  • double を int にキャストする位置を間違える
  • 上限・下限の含み方を誤解する

といったケースです。

これらはコード自体は動くため、
間違いに気づきにくいのが厄介な点です。

この記事では、後のセクションで、

  • なぜ偏るのか
  • どう書けば正しくなるのか

を、具体例とともに解説していきます。

3. まずは最短で使える Math.random() の基礎

ここからは、実際にJavaで乱数を生成する方法を見ていきます。
最初に紹介するのは、最も手軽で、初心者が最初に触れることが多い Math.random() です。

3.1 Math.random() とは何か

Math.random() は、Javaに標準で用意されている静的メソッドで、
0以上1未満の double 型の乱数を返します。

double value = Math.random();

このコードを実行すると、次のような値が返ります。

  • 0.0 以上
  • 1.0 未満

つまり、1.0 は絶対に含まれません

3.2 なぜ Math.random() は手軽なのか

Math.random() の最大の特徴は、
準備が一切いらないことです。

  • クラスの生成が不要
  • import も不要
  • 1行で使える

このため、

  • 学習用のサンプル
  • 簡単なデモ
  • 処理の流れを確認したいとき

といった場面では非常に便利です。

3.3 Math.random() で整数の乱数を作る方法

実際のプログラムでは、double ではなく
整数の乱数が欲しくなる場面が多くあります。

3.3.1 0〜9 の乱数を作る

int value = (int)(Math.random() * 10);

このコードで得られる値は、

  • 0 以上
  • 9 以下

になります。

理由は次の通りです。

  • Math.random() は 0.0 〜 0.999…
  • それに 10 を掛けると 0.0 〜 9.999…
  • int にキャストすると小数点以下が切り捨てられる

3.4 1〜10 の乱数を作るときの注意点

初心者がよく間違えるのが、開始値をずらす位置です。

int value = (int)(Math.random() * 10) + 1;

この場合、

  • 1 以上
  • 10 以下

の乱数が得られます。

順序を間違えて、次のように書くと結果が変わります。

// よくある間違い
int value = (int)Math.random() * 10 + 1;

このコードは、

  • (int)Math.random() が常に 0
  • 結果として常に 1 になる

というバグを生みます。

キャストの位置は必ず括弧で囲む、という点は重要です。

3.5 Math.random() のメリットと限界

メリット

  • 非常に簡単
  • 学習コストが低い
  • ちょっとした用途には十分

限界

  • 乱数の再現性(シード指定)ができない
  • 内部制御ができない
  • セキュリティ用途には使えない
  • 複雑な用途では柔軟性が低い

特に、

  • テストで同じ乱数を使いたい
  • 毎回異なる振る舞いを細かく制御したい

といった要件が出てきた場合、
Math.random() では対応できません。

3.6 Math.random() はどんな場面で使うべきか

Math.random() は、次のような場面に向いています。

  • Javaの学習初期
  • アルゴリズムの説明用コード
  • 動作確認用の簡易サンプル

逆に、

  • 業務アプリ
  • テストコード
  • セキュリティが絡む処理

では、より適切な乱数生成手段を選ぶ必要があります。

4. 基本クラス java.util.Random を理解する

ここからは、Math.random() よりも一段階踏み込んだ方法として、
java.util.Random クラスを解説します。

Random は、Javaで乱数を扱ううえで長年使われてきた基本的なクラスで、
「乱数をきちんと制御したい」という場面で登場します。

4.1 Random クラスとは何か

Random は、擬似乱数を生成するためのクラスです。
次のようにインスタンスを作成して使います。

import java.util.Random;

Random random = new Random();
int value = random.nextInt();

Math.random() との大きな違いは、
乱数生成器をオブジェクトとして扱える点にあります。

これにより、

  • 同じ乱数生成器を使い回す
  • 振る舞いを一定に保つ
  • 設計として乱数を明示的に扱う

といったことが可能になります。

4.2 Random で生成できる主な乱数

Random クラスでは、用途に応じたメソッドが用意されています。

代表的なものは次の通りです。

  • nextInt():int 型の乱数
  • nextInt(bound):0以上 bound 未満の int
  • nextLong():long 型の乱数
  • nextDouble():0.0以上1.0未満の double
  • nextBoolean():true / false

これらを使い分けることで、
型に応じた自然な乱数生成ができます。

4.3 nextInt(bound) の正しい理解

Random を使ううえで最も重要なのが、nextInt(bound) の挙動です。

Random random = new Random();
int value = random.nextInt(10);

この場合に生成される値は、

  • 0 以上
  • 10 未満(0〜9)

です。

上限は含まれない、という点は非常に重要です。
これは Java の乱数API全体に共通する考え方でもあります。

4.4 任意の範囲(例:1〜10)を作る方法

「1〜10 の乱数を作りたい」という場合は、
次のように書きます。

int value = random.nextInt(10) + 1;

考え方はシンプルです。

  1. nextInt(10) で 0〜9 を作る
  2. それに 1 を足して 1〜10 にずらす

この「まず0始まりで作ってからずらす」という発想は、
今後のすべての範囲指定で使います。

4.5 マイナス値を含む範囲の乱数

例えば、

  • -5 〜 4 の乱数を作りたい

場合は、次のように考えます。

int value = random.nextInt(10) - 5;
  • nextInt(10) → 0〜9
  • そこから 5 を引く → -5〜4

範囲指定は常に、

  • 幅(何個の値があるか)
  • 開始位置(どこから始めたいか)

の2つに分けて考えると、混乱しにくくなります。

4.6 シード(seed)とは何か

Random クラスの大きな特徴が、**シード(seed)**です。

シードとは、

  • 乱数生成の出発点となる値

のことです。

Random random = new Random(12345L);

このようにシードを指定すると、

  • プログラムを何度実行しても
  • 同じ順序で
  • 同じ乱数列が生成されます

4.7 シード指定が役立つ場面

シード指定は、次のような場面で非常に有効です。

  • テストコード
  • デバッグ
  • シミュレーション結果の再現

例えば、

  • 「この入力でこの結果になるはず」
  • 「このバグをもう一度再現したい」

といった場合、シードを固定しておくことで
完全に同じ乱数環境を再現できます。

4.8 シード指定が危険になる場面

一方で、シード指定は万能ではありません

次のような用途では、Random を使うべきではありません。

  • パスワード生成
  • 認証トークン
  • セッションID

なぜなら、Random の乱数は、

  • アルゴリズムが公開されている
  • 出力から内部状態を推測される可能性がある

ためです。

「動くから大丈夫」ではなく、
用途に合った乱数生成器を選ぶことが重要です。

4.9 Random のメリットと注意点

メリット

  • 扱いやすい
  • 再現性を持たせられる
  • テストや業務ロジックで使いやすい

注意点

  • セキュリティ用途には不向き
  • 高負荷・並列処理では性能問題が出る場合がある
  • 新しいJavaでは、より適した選択肢が存在する

5. マルチスレッド環境では ThreadLocalRandom を使う

前のセクションで紹介した Random は便利ですが、
マルチスレッド環境では注意が必要です。

このセクションでは、並列処理でよく使われる
ThreadLocalRandom について、初心者にも分かる形で解説します。

5.1 マルチスレッドで Random を使うと何が起きるのか

Webアプリケーションやサーバー処理では、
複数のスレッドが同時に動くことが一般的です。

このような環境で、1つの Random インスタンスを共有すると、

  • 内部状態の更新で競合が発生する
  • ロックによる待ち時間が増える
  • 思ったより処理が遅くなる

といった問題が起きやすくなります。

コード自体は正しくても、
パフォーマンスが悪化するのが問題です。

5.2 ThreadLocalRandom とは何か

ThreadLocalRandom は、その名の通り、

  • 各スレッドごとに
  • 専用の乱数生成器を持つ

仕組みを提供するクラスです。

これにより、

  • スレッド間の競合が起きない
  • ロックが不要
  • 高速に乱数を生成できる

というメリットがあります。

5.3 ThreadLocalRandom の基本的な使い方

ThreadLocalRandom は、インスタンスを生成しません。
次のようにして使います。

import java.util.concurrent.ThreadLocalRandom;

int value = ThreadLocalRandom.current().nextInt();

current() を呼び出すことで、

  • 現在のスレッド専用の乱数生成器

が取得されます。

5.4 範囲指定の書き方(重要)

ThreadLocalRandom では、範囲指定が非常に分かりやすくなっています。

5.4.1 0〜9 の乱数

int value = ThreadLocalRandom.current().nextInt(0, 10);

生成される値は、

  • 0 以上
  • 10 未満

です。

5.4.2 1〜10 の乱数

int value = ThreadLocalRandom.current().nextInt(1, 11);

このように、

  • 開始値(含む)
  • 終了値(含まない)

をそのまま書けるため、
計算ミスが起きにくいのが特徴です。

5.5 なぜ「終了値は含まれない」のか

Javaの乱数APIでは、基本的に、

  • 下限:含む
  • 上限:含まない

という設計が採用されています。

この設計には次の利点があります。

  • 配列のインデックスと相性が良い
  • 範囲の幅が end - start で直感的
  • オフバイワンエラーを減らせる

最初は違和感があるかもしれませんが、
慣れると非常に扱いやすい考え方です。

5.6 ThreadLocalRandom のメリットと注意点

メリット

  • マルチスレッド環境で高速
  • 範囲指定が分かりやすい
  • 余計なインスタンス管理が不要

注意点

  • シードを明示的に指定できない
  • 再現性が必要なテストには向かない
  • 単純なシングルスレッド用途では必須ではない

5.7 どんな場面で使うべきか

ThreadLocalRandom は、次のような場面に向いています。

  • Webアプリケーション
  • 並列処理を行うバッチ
  • マルチスレッド前提のサービス処理

逆に、

  • 再現性が必要なテスト
  • 単純な学習用コード

では、RandomMath.random() のほうが適切な場合もあります。

6. Java 17以降の新しい選択肢 RandomGenerator

Java 17以降では、乱数生成の考え方が一段進化しました。
その中心となるのが RandomGenerator です。

このセクションでは、

  • なぜ新しいAPIが必要になったのか
  • 従来の Random と何が違うのか
  • どんな場面で使うべきか

を、初心者にも分かるように整理します。

6.1 なぜ新しい乱数APIが導入されたのか

これまでJavaでは、Random が事実上の標準でした。
しかし、長年の利用を通して次の課題が見えてきました。

  • アルゴリズムが固定されている
  • 用途に応じた選択がしにくい
  • 高品質な乱数と高速な乱数の切り替えが難しい

つまり、
「とりあえず Random」では済まなくなってきた
という背景があります。

そこで導入されたのが、
アルゴリズムを切り替えられる共通インターフェースとしての
RandomGenerator です。

6.2 RandomGenerator とは何か

RandomGenerator は、

  • 乱数生成器の共通インターフェース
  • 実装クラスを自由に選択できる
  • APIは統一されている

という特徴を持ちます。

重要なのは、
「乱数をどう作るか」よりも「どう使うか」に集中できる設計
になっている点です。

6.3 RandomGenerator の基本的な使い方

まずは、最もシンプルな例を見てみましょう。

import java.util.random.RandomGenerator;

RandomGenerator random = RandomGenerator.getDefault();
int value = random.nextInt(10);

このコードでは、

  • 実行環境に適したデフォルトの乱数生成器
  • 0以上10未満の整数乱数

が生成されます。

見た目は Random とよく似ていますが、
内部の設計思想はまったく異なります

6.4 範囲指定がより直感的になった点

RandomGenerator では、範囲指定がさらに分かりやすくなっています。

int value = random.nextInt(1, 11);

この場合、

  • 1 を含む
  • 11 は含まない

つまり、結果は 1〜10 になります。

ThreadLocalRandom と同じ考え方なので、
新旧APIで混乱しにくいのが大きな利点です。

6.5 ストリームとして乱数を扱える

RandomGenerator の特徴のひとつが、
乱数をストリームとして扱える点です。

random.ints(5, 1, 11).forEach(System.out::println);

このコードでは、

  • 1〜10 の乱数を
  • 5個連続で生成

できます。

ループを書かずに済むため、

  • コードが読みやすい
  • 処理の意図が明確になる

というメリットがあります。

6.6 アルゴリズムを選択できるという発想

RandomGenerator では、用途に応じて
乱数生成アルゴリズムを選択できます。

考え方としては、

  • 高速性重視
  • 乱数品質重視
  • 再現性重視

といった目的に応じて、
最適な生成器を選ぶ、というものです。

初心者のうちは、

  • デフォルト実装を使う

だけで十分ですが、
拡張性を考えると非常に大きな進化と言えます。

6.7 Random との使い分け

ここまでを整理すると、次のようになります。

  • Java 8 以前が前提 → Random
  • マルチスレッド重視 → ThreadLocalRandom
  • Java 17以降で新規実装 → RandomGenerator

特別な理由がなければ、
新しいコードでは RandomGenerator を選ぶ
という判断も十分に合理的です。

6.8 初心者が無理に使わなくてもよい理由

とはいえ、RandomGenerator は必須ではありません。

  • 学習初期
  • 小規模なサンプル
  • 単純なロジック確認

では、RandomMath.random() のほうが
理解しやすい場合もあります。

重要なのは、

なぜこのAPIを選んだのか説明できること

です。

7. セキュリティ用途では SecureRandom を使うべき理由

ここまで紹介してきた乱数生成方法は、
「見た目がランダム」であることを重視したものです。

しかし、このセクションで扱う SecureRandom は、
「予測されないこと」を最優先にした、まったく別の目的を持つ乱数生成器です。

7.1 SecureRandom とは何か

SecureRandom は、
セキュリティ用途専用に設計された乱数生成クラスです。

import java.security.SecureRandom;

SecureRandom random = new SecureRandom();

このクラスは、

  • 内部状態を推測されにくい
  • 外部から予測されない
  • 暗号用途を前提としている

という特徴を持っています。

7.2 なぜ通常の乱数ではダメなのか

RandomMath.random() は、

  • 高速
  • 再現性がある
  • 設計がシンプル

という利点があります。

しかし、これらはすべて
セキュリティの観点では欠点になります。

例えば、

  • 同じシードから同じ乱数列が生成される
  • 出力結果から内部状態を推測できる可能性がある

という性質があるため、

  • パスワード
  • トークン
  • セッションID

といった用途には致命的に不向きです。

7.3 SecureRandom の基本的な使い方

SecureRandom の基本的な使い方は次の通りです。

SecureRandom random = new SecureRandom();
int value = random.nextInt(10);

この場合、

  • 0以上
  • 10未満

の整数乱数が生成されます。

APIの使い方自体は Random と似ていますが、
内部の生成方法が大きく異なる点が重要です。

7.4 バイト配列として乱数を生成する

セキュリティ用途では、
整数よりもバイト配列として乱数を扱うことが多くなります。

byte[] bytes = new byte[16];
random.nextBytes(bytes);

このコードでは、

  • 16バイト分の
  • 予測困難な乱数データ

が生成されます。

この形式は、

  • トークン
  • 鍵素材
  • 一時ID

などにそのまま使えます。

7.5 文字列トークンを作るときの考え方

実務では、乱数をそのまま使うのではなく、

  • Base64
  • 16進数(Hex)

などに変換して使うことが一般的です。

重要なのは、

  • 文字数ではなくビット長
  • 見た目よりも予測困難性

を重視することです。

7.6 SecureRandom の注意点

SecureRandom は万能ではありません。

注意点

  • Random より遅い
  • 初期化に時間がかかる場合がある
  • 乱数の再現性は基本的にない

そのため、

  • ゲーム演出
  • 単なるサンプル
  • 高頻度で大量生成する処理

には向いていません。

7.7 どんな場面で必ず使うべきか

次の用途では、
迷わず SecureRandom を選ぶべきです。

  • パスワード生成
  • 認証トークン
  • セッションID
  • ワンタイムキー
  • セキュリティに関わるID全般

「たまたま問題が起きていない」だけで、
潜在的に危険な実装になっているケースは非常に多いです。

8. 範囲指定の完全パターン集(ここが一番つまずきやすい)

このセクションでは、Javaの乱数で**最も質問が多い「範囲指定」**を、
型別・ケース別に整理します。

ここを理解できれば、
「思った範囲の乱数が出ない」問題はほぼ解消できます。

8.1 Javaの乱数に共通する基本ルール

まず、Javaの乱数APIに共通する大原則です。

  • 下限(start / origin)は 含まれる
  • 上限(end / bound)は 含まれない

このルールは、

  • Random
  • ThreadLocalRandom
  • RandomGenerator

すべてに共通しています。

この前提を忘れると、
どんなコードを書いても混乱します。

8.2 int型の範囲指定パターン

8.2.1 0〜n-1 の乱数(最基本)

int value = random.nextInt(n);

結果:

  • 0 以上
  • n 未満

配列のインデックスと相性が良い、
最も基本的なパターンです。

8.2.2 1〜n の乱数

int value = random.nextInt(n) + 1;

考え方:

  1. 0〜n-1 を作る
  2. 1 を足して 1〜n にずらす

8.2.3 a〜b(両端を含む)乱数

int value = random.nextInt(b - a + 1) + a;

例:5〜10 の場合

int value = random.nextInt(6) + 5;

8.3 マイナス値を含む範囲

8.3.1 -10〜9 の乱数

int value = random.nextInt(20) - 10;

考え方:

  • 幅:20(-10 〜 9)
  • 開始位置:-10

8.3.2 任意の負数範囲 a〜b

int value = random.nextInt(b - a + 1) + a;

a が負数でも、考え方は同じです。

8.4 long型の範囲指定

long 型も、考え方は int と同じです。

long value = random.nextLong(bound);

または範囲指定

long value = random.nextLong(origin, bound);

こちらも、

  • origin:含む
  • bound:含まない

です。

8.5 double型の範囲指定

8.5.1 0.0〜1.0未満(基本)

double value = random.nextDouble();

8.5.2 0.0〜n 未満

double value = random.nextDouble() * n;

8.5.3 a〜b の小数乱数

double value = a + random.nextDouble() * (b - a);

この場合も、

  • a は含まれる
  • b は基本的に含まれない

と考えるのが安全です。

8.6 boolean型の乱数

boolean value = random.nextBoolean();

用途:

  • コイントス
  • ランダム分岐
  • テスト用の条件分岐

8.7 よくある間違いと失敗例

キャストの位置ミス

// 間違い
int value = (int)Math.random() * 10;

常に 0 になります。

% 演算による偏り

// 非推奨
int value = random.nextInt() % 10;

負数や偏りが発生しやすいため、
必ず nextInt(bound) を使いましょう。

9. 再現性(seed)とテストでの正しい使い方

このセクションでは、乱数を扱ううえで非常に重要な
「再現性(seed)」について解説します。

乱数は本来ランダムですが、
あえて同じ結果を再現できるようにすることが、
実務では大きな価値を持ちます。

9.1 seed(シード)とは何か

seed(シード)とは、

  • 乱数生成の出発点となる値
  • 乱数列の「初期状態」

のことです。

擬似乱数は、

  1. seed をもとに
  2. 一定の計算ルールで
  3. 次々と値を生成する

という仕組みになっています。

そのため、

  • 同じアルゴリズム
  • 同じ seed

を使えば、必ず同じ順序で乱数が生成されます。

9.2 seedを指定する方法(Random の例)

Random クラスでは、コンストラクタで seed を指定できます。

Random random = new Random(12345L);

この random を使って生成した乱数は、

  • プログラムを何度実行しても
  • 常に同じ結果

になります。

9.3 再現性が役立つ具体的な場面

再現性は、特に次のような場面で重要です。

9.3.1 テストコード

  • テストが毎回違う結果になる
  • 失敗したり成功したりする

こうしたテストは、
信頼できないテストになってしまいます。

seed を固定すれば、

  • 毎回同じ乱数
  • 毎回同じ結果

が得られ、安定したテストが可能になります。

9.3.2 デバッグ・バグ再現

  • 特定条件でのみ発生する不具合
  • 乱数が絡む処理のバグ

これらを調査する際、
同じ乱数列を再現できることは非常に重要です。

9.4 再現性を使うべきでない場面

一方で、再現性は危険にもなり得ます

次の用途では、seed を固定してはいけません。

  • パスワード生成
  • 認証トークン
  • セッションID
  • セキュリティ関連のキー

これらは、

  • 再現できる
  • 予測できる

だけで、致命的な問題になります。

9.5 「テスト用」と「本番用」を分ける考え方

実務では、次のように使い分けるのが一般的です。

  • テスト・検証環境
    → seed を固定
  • 本番環境
    → seed を固定しない(または SecureRandom

この切り替えを明確にしておくことで、

  • 安定したテスト
  • 安全な本番運用

の両立が可能になります。

9.6 ThreadLocalRandomSecureRandom と再現性

  • ThreadLocalRandom
    → 明示的な seed 指定はできない
  • SecureRandom
    → 再現性を持たせる設計ではない

つまり、
再現性が必要なら RandomRandomGenerator
予測不能性が必要なら SecureRandom
という判断になります。

10. 実務でよく使われる乱数の具体例(そのまま使える)

ここでは、実務や学習で頻出する乱数の使い方を、
理由付き・コピペ可能な形で紹介します。

「結局どう書けばいいのか」が一目で分かるセクションです。

10.1 サイコロ(1〜6)を振る

最も定番の例です。

int dice = ThreadLocalRandom.current().nextInt(1, 7);
  • 1:含む
  • 7:含まない
    → 結果は 1〜6

なぜこの書き方が良いか

  • 範囲が直感的
  • 計算ミスが起きにくい
  • マルチスレッドでも安全

10.2 くじ引き・抽選(0〜n-1)

配列やリストからランダムに1つ選ぶケースです。

int index = ThreadLocalRandom.current().nextInt(list.size());
Object item = list.get(index);

この書き方は、

  • 配列インデックスと完全一致
  • 余計な計算が不要

という理由で、最も安全なパターンです。

10.3 リストをランダムにシャッフルする

順番をランダムに並び替えたい場合は、
自前で乱数処理を書く必要はありません。

Collections.shuffle(list);

内部では適切な乱数が使われており、

  • 可読性が高い
  • バグが入りにくい

というメリットがあります。

10.4 ランダムな true / false(分岐用)

確率50%の分岐を作りたい場合です。

boolean flag = ThreadLocalRandom.current().nextBoolean();

用途例:

  • ランダムイベント発生
  • テスト用の条件分岐
  • シミュレーション

10.5 ランダムな数値ID(非セキュリティ用途)

単なる識別子やテスト用IDであれば、
次のような実装で十分な場合もあります。

int id = ThreadLocalRandom.current().nextInt(100000, 1000000);
  • 6桁のランダムID
  • 見た目がランダムであれば良い用途向け

注意
認証やセキュリティには使ってはいけません。

10.6 セキュリティ用途のランダムトークン

認証トークンやワンタイムキーの場合は、
必ず SecureRandom を使います。

SecureRandom random = new SecureRandom();
byte[] bytes = new byte[32];
random.nextBytes(bytes);

これを文字列化して使うのが一般的です。

String token = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);

ポイント

  • バイト長で安全性を確保
  • 見た目より「予測不能性」を重視

10.7 テスト用に再現できる乱数

テストコードでは、再現性が重要です。

Random random = new Random(42L);
int value = random.nextInt(100);
  • seed を固定
  • 毎回同じ結果

これにより、

  • テストの安定性
  • デバッグのしやすさ

が大きく向上します。

11. よくあるミス・落とし穴(ここで事故が起きやすい)

このセクションでは、
Javaの乱数で特に多い失敗例をまとめます。

コードは動いているのに、
「実は間違っている」「将来問題になる」
というケースが多いため、必ず一度は目を通してください。

11.1 キャストの位置ミスによるバグ

初心者が最もやりがちなミスです。

// 間違い
int value = (int)Math.random() * 10;

このコードでは、

  • (int)Math.random() が常に 0
  • 結果として value は常に 0

になります。

正しい書き方

int value = (int)(Math.random() * 10);

ポイント

  • キャストは必ず最後
  • 括弧を忘れない

11.2 上限が「含まれる」と思い込む

次のコードで、

int value = random.nextInt(10);

「0〜10 が出る」と思ってしまうケースは非常に多いです。

実際は、

  • 0 以上
  • 10 未満(0〜9)

です。

対策

  • 「上限は含まれない」と口に出して確認
  • 範囲は必ず紙に書いて考える

11.3 %(剰余演算)で範囲を作る

次のようなコードを見かけることがあります。

int value = random.nextInt() % 10;

問題点

  • 負数が出る
  • 値の分布が偏る
  • 予測しづらい挙動になる

必ず nextInt(bound) を使いましょう。

11.4 セキュリティ用途に Random を使う

見た目は動いているため、
レビューされるまで気づかれないこともあります。

// 危険な例
String token = Integer.toString(random.nextInt());

この実装は、

  • 予測可能
  • 再現可能
  • 攻撃に弱い

という問題を抱えています。

セキュリティ用途では必ず SecureRandom を使う
というルールを覚えておきましょう。

11.5 毎回 new Random() を作る

ループ内で次のように書くと、

for (...) {
    Random random = new Random();
    int value = random.nextInt();
}
  • 不要なオブジェクト生成
  • 意図しない同一シードの可能性

が発生します。

対策

  • 1つのインスタンスを使い回す
  • または ThreadLocalRandom を使う

11.6 「なんとなく動く」コードを放置する

乱数処理は、

  • テストで気づきにくい
  • バグが表面化しにくい

という特徴があります。

そのため、

  • 理由を説明できないコード
  • 書いた本人しか分からない処理

は、将来必ず問題になります。

「なぜこの乱数APIを選んだのか」
を説明できることが重要です。

12. FAQ(Javaの乱数でよくある質問)

最後に、Javaの乱数について特によく聞かれる質問をまとめます。
ここを読むことで、この記事全体の理解を整理できます。

12.1 Javaで乱数を生成するなら、結局どれを使えばいいですか?

用途によって答えが変わります。

  • 学習・簡易サンプル
    Math.random()
  • 再現性が必要な処理・テスト
    Random(seed指定)
  • マルチスレッド環境
    ThreadLocalRandom
  • Java 17以降の新規実装
    RandomGenerator
  • セキュリティ用途
    SecureRandom

「とりあえずこれ」という万能な方法はありません。
用途で選ぶのが正解です。

12.2 nextInt(a, b) の a と b はどちらが含まれますか?

  • a は含まれる
  • b は含まれません

例えば、

ThreadLocalRandom.current().nextInt(1, 11);

この場合、結果は 1〜10 です。

Javaの乱数APIでは、
「下限は含む、上限は含まない」
が基本ルールです。

12.3 乱数の結果が偏っている気がするのですが?

多くの場合、実装の問題です。

よくある原因は次の通りです。

  • %(剰余演算)を使っている
  • キャストの位置を間違えている
  • 範囲指定の計算が誤っている

正しく nextInt(bound)nextInt(origin, bound) を使えば、基本的に一様な分布になります。

12.4 Math.random() は使ってはいけないのですか?

使ってはいけないわけではありません。

  • 学習用
  • サンプルコード
  • 動作確認

では十分に役立ちます。

ただし、

  • 再現性が必要
  • セキュリティが絡む
  • 実務コードとして長く使う

場合は、より適切なAPIを選びましょう。

12.5 セキュリティ用途に Random を使うと何が危険ですか?

Random の乱数は、

  • 内部状態が推測可能
  • 同じseedで再現可能

という性質があります。

そのため、

  • トークン
  • セッションID
  • パスワード

に使うと、攻撃者に予測される可能性があります。

セキュリティ用途では、必ず SecureRandom を使用してください。

12.6 テストと本番で乱数の扱いを変えるべきですか?

はい、変えるべきです。

  • テスト環境
    → seed固定(再現性重視)
  • 本番環境
    → seed固定なし、または SecureRandom

この切り分けを意識するだけで、

  • テストの安定性
  • 本番の安全性

の両立が可能になります。

12.7 Javaのバージョンが古い場合はどうすればいいですか?

Java 8 や 11 の場合は、

  • Random
  • ThreadLocalRandom
  • SecureRandom

を正しく使い分ければ問題ありません。

Java 17以降を使える環境であれば、
新規コードでは RandomGenerator を検討すると良いでしょう。

これで、「java 乱数」に関する基礎から実務までの解説は完了です。
「なぜその書き方なのか」を説明できる状態になっていれば、
乱数処理で困ることはほとんどなくなるはずです