Java | Java 標準ライブラリ:Random

Java Java
スポンサーリンク

Random クラスの全体像をまずつかむ

java.util.Random は、
「それっぽくランダムな値をくれる“乱数生成器”のクラス」です。

ゲームでダメージ値をランダムにしたい
ガチャの結果をランダムにしたい
テストデータを適当に作りたい

そんなときに使うのが Random です。

Math.random() でも乱数は取れますが、
Random のほうが「インスタンスとして持てる」「いろんな型の乱数が取れる」「シードを管理できる」
といった点で、一段階ちゃんとした道具になっています。


基本の使い方と「一番最初の罠」

Random インスタンスを作ってから使う

一番シンプルな使い方はこうです。

import java.util.Random;

public class RandomSample {
    public static void main(String[] args) {
        Random random = new Random();          // 乱数生成器を1つ作る

        int r1 = random.nextInt();            // int の乱数(範囲は int 全体)
        int r2 = random.nextInt(10);          // 0〜9 の乱数
        boolean b = random.nextBoolean();     // true / false の乱数

        System.out.println(r1);
        System.out.println(r2);
        System.out.println(b);
    }
}
Java

この「new Random() して、同じインスタンスを何度も使い回す」という形が基本です。

毎回 new Random() するのはやってはいけない(重要)

初心者がやりがちなのが、必要なたびに new Random() してしまうパターンです。

int r1 = new Random().nextInt(10);
int r2 = new Random().nextInt(10);
int r3 = new Random().nextInt(10);
Java

これはよくありません。

Random は内部に「状態(シードから進んでいく現在位置)」を持っていて、
同じインスタンスを使い続けることで「乱数列」がうまく機能します。

毎回 new してしまうと、

短時間に連続で new すると、内部の初期シードが似通ってしまう
結果として「パッと見あまりランダムに見えない」出方になる可能性がある

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

基本ルールとして、

「Random は1つ作って、それをずっと使いまわす」

と覚えておいてください。


nextInt / nextBoolean / nextDouble の違いと使いどころ

nextInt() と nextInt(bound) の違い

nextInt() は、int の全範囲から乱数を返します。
ですが、多くの場合それでは範囲が広すぎます。

よく使うのは nextInt(bound) のほうです。

Random random = new Random();

int n = random.nextInt(10);  // 0〜9 のどれか
Java

nextInt(10) は「0 以上 10 未満の整数」と覚えてください。

サイコロの 1〜6 を作りたいなら、こうなります。

int dice = random.nextInt(6) + 1;   // 0〜5 に 1 を足して 1〜6
Java

0 始まりか 1 始まりか、+1 を付けるかどうかで、
欲しい範囲に合わせて調整していきます。

nextBoolean() で true / false をランダムに

boolean hit = random.nextBoolean();  // true か false がランダムに返る
Java

コイントス(表 / 裏)や、
「50% の確率でイベントを起こしたい」
といった場面にそのまま使えます。

例えば、30% の確率で何かを起こしたいなら、nextInt(100) などと組み合わせます。

int r = random.nextInt(100);  // 0〜99
if (r < 30) {
    // 30% の確率でここに入る
}
Java

nextDouble() で 0.0〜1.0 未満の小数

nextDouble()0.0 <= x < 1.0 の範囲の double を返します。

double r = random.nextDouble();   // 0.0〜1.0 未満
Java

範囲を変えたいときは、掛け算・足し算で調整します。

0.0〜5.0 未満なら

double v = random.nextDouble() * 5.0;
Java

1.0〜6.0 の間(連続値)なら

double v = random.nextDouble() * 5.0 + 1.0;
Java

nextDouble() の結果に何を掛けて、何を足すか」で、
欲しい範囲の連続値乱数を作るイメージです。


シード(seed)の意味と「同じ乱数列を再現する」という話

new Random() の中では何が起きているか

Random は、「シード(seed)」という初期値をもとに乱数列を作ります。

new Random() と引数なしで呼ぶと、
「現在時刻」などを使って、毎回違うシードが設定されます。

だから普通は

プログラムを実行するたびに、違う乱数列が出る

という動きになります。

あえて seed を指定することで「再現可能な乱数」にする

テストやデバッグでは、
「毎回同じ乱数列が欲しい」ことがあります。

その場合は、seed を明示的に指定します。

Random random1 = new Random(12345L);  // シードに 12345 を指定
Random random2 = new Random(12345L);  // 同じシード

System.out.println(random1.nextInt(100));
System.out.println(random1.nextInt(100));

System.out.println(random2.nextInt(100));
System.out.println(random2.nextInt(100));
Java

random1random2 は、同じシードで初期化しているので、
出てくる乱数の列も同じになります。

例えばゲームで、「シード 12345 のステージ」を作る、
みたいなことが可能になりますし、

テストで「乱数が絡む処理を、いつも同じ条件で検証したい」
というときにも役立ちます。


Random と Math.random() の違いと使い分け

Math.random() は「手軽なショートカット」、Random は「ちゃんとした道具」

Math.random() は内部的に Random を使っていますが、
毎回「共有の Random」を使って nextDouble() しているイメージです。

double r = Math.random();   // 0.0〜1.0 未満
Java

ちょっとした用途ならこれで十分です。

一方で、次のようなときは Random を使うほうが向いています。

自分でシードを管理したい(テストで再現性が必要)
int や boolean など、いろいろな型の乱数がほしい
1つの Random を複数のメソッドで共有して使いたい

Random を明示的に使えば、
「乱数の元」がどこにあるか、コード上ではっきり見えるようになります。


「乱数っぽさ」を過信しすぎないために

Random は「疑似乱数」であって、本当に完全ランダムではない

Random が出してくれる値は、
数学的には「疑似乱数(pseudo random)」と呼ばれます。

シード値とアルゴリズムさえ分かれば、
実は完全に未来まで予測できます。

多くの用途(ゲーム・簡単なシャッフル・テストデータ作成など)ではこれで十分ですが、
セキュリティ(暗号・トークン生成)などには使ってはいけません。

そういう場面では、SecureRandom という別クラスを使います。
初心者のうちは、

「セキュリティに関わるランダムは SecureRandom、それ以外の普通のランダムは Random」

くらいのイメージで覚えておくとよいです。


よくある「やりがちミス」とその直し方

毎回 new Random() してしまう

さっき触れた通り、乱数が必要になるたびに

int x = new Random().nextInt(10);
Java

と書くのはやめたほうがいいです。

クラス内で 1 つだけ Random を持っておき、それを使い回す形にしましょう。

import java.util.Random;

public class MyGame {
    private final Random random = new Random();

    public int rollDice() {
        return random.nextInt(6) + 1;
    }
}
Java

範囲計算で off-by-one(1 ずれ)する

「0〜9 なのか、1〜10 なのか」を意識せずに書くと、
「10 が絶対出ない」「0 が出ちゃダメなのに出ている」などのバグになります。

nextInt(bound) は「0 以上 bound 未満」
1〜N にしたかったら「nextInt(N) + 1

と、言語化して覚えておいてください。

具体例:

1〜10 → nextInt(10) + 1
0〜99 → nextInt(100)
5〜15 → nextInt(11) + 5 (0〜10 に 5 を足す)

この辺りは、「紙に書いて範囲を確認する」癖をつけると、徐々に身体で覚えていきます。


まとめ:Random をどういう感覚で自分の武器にするか

Random を、初心者向けに一言で表すと、

「シードを持った“乱数生成器”インスタンス。1つ作って色んな乱数を吐かせる道具」

です。

頭に置いておきたいポイントは、

  • Random は1回 new して、それを使い回す
  • nextInt(bound) は 0〜bound-1、1〜N は nextInt(N) + 1
  • テストで毎回同じ乱数がほしければ、new Random(固定シード) を使う
  • セキュリティ用途には Random ではなく SecureRandom(これは別ジャンル)

あたりです。

タイトルとURLをコピーしました