乱数生成(Random)
この章で得られるスキル:
- ✅ ランダムな数を使ったゲームやアプリが作れる
- ✅ じゃんけん、サイコロ、くじ引きなどを実装できる
- ✅ 実行するたびに違う動作をするプログラムが書ける
- ✅ シミュレーションやテストデータ生成ができる
Step 0: 乱数がないとどうなる?
まず、乱数を使わないプログラムを見てみよう。
予測可能なゲーム
以下は、サイコロの出目をシミュレートしようとしたプログラムである:
このプログラムには致命的な問題がある:
- 常に同じ結果になる:何度実行しても3が出る
- ゲームとして成立しない:結果が予測できてしまう
- 現実のサイコロを再現できない:1〜6のランダムな値が必要
現実世界での乱数の必要性
乱数が必要な場面は多い:
- ゲーム:サイコロ、カード、敵の行動パターン
- 抽選:当選者の選定、ランダムな順序
- シミュレーション:株価の変動、天気の予測
- セキュリティ:パスワード生成、暗号化キー
では、どうやってランダムな値を生成するのか?
Step 1: 乱数とは
乱数の定義
乱数(らんすう、Random Number) とは、予測不可能な値のことである。
プログラムでは、 疑似乱数 という「本物の乱数のように見える数」を使う:
- シード(seed):乱数生成の開始点
- 疑似乱数:計算で作られた、ランダムに見える数
- 真の乱数:物理現象から生成される、完全にランダムな数
Javaの Random クラスは疑似乱数を生成する。
シードが同じなら同じ乱数列が生成されるが、通常は現在時刻をシードにするため、毎回異なる値になる。
Randomクラスの登場
Javaでは、 java.util.Random クラスを使って乱数を生成する:
import java.util.Random;
Random random = new Random();
int number = random.nextInt(); // ランダムな整数
これで、予測不可能な値を生成できるようになる。
Step 2: Randomの基本的な使い方
Randomクラスを使う4つのステップ
Randomクラスの使い方は、Scannerと同じく4ステップである:
基本形
以下が最も基本的な形である:
実行結果例:
生成された乱数: 7
実行するたびに異なる値が出力される。
コードの解説
Random random = new Random();
Random:乱数生成器のクラスrandom:変数名(慣例的にrandomを使う)new Random():新しい乱数生成器を作成
int number = random.nextInt(10);
nextInt(10):0〜9の範囲でランダムな整数を生成- 戻り値:
int型の乱数
nextInt(10) は 0以上10未満 の整数を返す。
つまり、0, 1, 2, 3, 4, 5, 6, 7, 8, 9 のいずれか。10は含まれない。
Step 3: 様々な乱数生成メソッド
Randomクラスには、さまざまな種類の乱数を生成するメソッドがある。
主な乱数生成メソッド
| メソッド | 戻り値の型 | 説明 | 範囲 |
|---|---|---|---|
nextInt() | int | ランダムな整数 | -2147483648 〜 2147483647 |
nextInt(n) | int | 0以上n未満の整数 | 0 〜 n-1 |
nextDouble() | double | ランダムな小数 | 0.0以上1.0未満 |
nextBoolean() | boolean | trueまたはfalse | true / false |
nextLong() | long | ランダムな長整数 | 非常に大きな範囲 |
各メソッドの使用例
実行結果例:
任意の整数: -1234567890
サイコロ: 4
確率: 0.7234892341
コイン: 表
Step 4: 範囲指定のテクニック
1〜6のサイコロを作る
問題:nextInt(6) は0〜5を返すが、サイコロは1〜6である。
解決策:生成した値に1を足す。
int dice = random.nextInt(6) + 1; // 0〜5 → 1〜6
なぜこうなるか:
nextInt(6)→ 0, 1, 2, 3, 4, 5 のいずれか+1→ 1, 2, 3, 4, 5, 6 のいずれか
10〜20の整数を作る
問題:10〜20の範囲の乱数を生成したい。
解決策:範囲の幅を計算し、最小値を足す。
int min = 10;
int max = 20;
int number = random.nextInt(max - min + 1) + min;
計算式の意味:
- 範囲の幅を計算:
max - min + 1 = 20 - 10 + 1 = 11 - 0〜10の乱数を生成:
nextInt(11)→ 0〜10 - 最小値を足す:
+10→ 10〜20
小数の範囲指定
問題:0.0以上100.0未満の範囲の小数を生成したい。
解決策:nextDouble() の結果に倍率を掛ける。
double score = random.nextDouble() * 100; // 0.0以上100.0未満
実践例
Step 5: シードを使った制御
シードとは
シード(seed) とは、乱数生成の開始点となる値である。
- 同じシード → 同じ乱数列
- 異なるシード → 異なる乱数列
シードを指定する理由
通常は指定しない方が良いが、以下の場合に有用である:
- デバッグ:再現可能なテストを行いたい
- ゲームのリプレイ:同じ展開を再現したい
- テストの自動化:予測可能な結果が必要
シードの指定方法
Random random = new Random(12345); // シードを指定
シード有無の比較
実行結果例:
--- シード指定なし ---
3 7 1 9 4
--- シード指定あり(12345) ---
7 3 9 1 0
--- 同じシード(12345)で再生成 ---
7 3 9 1 0
シード指定ありの2回は、まったく同じ乱数列になることに注目。
実際の開発では、通常はシードを指定しない。
new Random() とだけ書けば、現在時刻を元に自動的にシードが設定され、毎回異なる乱数が生成される。
Step 6: よくある使用パターン
パターン1: 配列からランダムに選ぶ
配列の要素をランダムに選択する:
パターン2: 配列をシャッフルする
配列の要素をランダムに並び替える(Fisher-Yatesアルゴリズム):
パターン3: 重み付き抽選
確率を指定して抽選する:
パターン4: ランダムな文字列生成
パスワードやIDをランダムに生成する:
Step 7: 実践課題
ここまで学んだ知識を使って、以下の3つの課題に挑戦しよう。
課題1: じゃんけんゲーム
要件:
- コンピュータがランダムに「グー」「チョキ」「パー」のいずれかを出す
- ユーザーは0(グー)、1(チョキ)、2(パー)を入力
- 勝敗を判定して表示
ヒント:
String[] hands = {"グー", "チョキ", "パー"};
int computer = random.nextInt(3); // 0, 1, 2のいずれか
期待される実行例:
じゃんけんゲーム!
0:グー, 1:チョキ, 2:パー > 0
あなた: グー
コンピュータ: チョキ
あなたの勝ち!
解答例を見る
1import java.util.Random;2import java.util.Scanner;34public class Main {5 public static void main(String[] args) {6 String[] hands = {"グー", "チョキ", "パー"};7 Random random = new Random();8 Scanner scanner = new Scanner(System.in);910 System.out.println("じゃんけんゲーム!");11 System.out.print("0:グー, 1:チョキ, 2:パー > ");12 int user = scanner.nextInt();1314 int computer = random.nextInt(3);1516 System.out.println("あなた: " + hands[user]);17 System.out.println("コンピュータ: " + hands[computer]);1819 if (user == computer) {20 System.out.println("引き分け!");21 } else if ((user == 0 && computer == 1) ||22 (user == 1 && computer == 2) ||23 (user == 2 && computer == 0)) {24 System.out.println("あなたの勝ち!");25 } else {26 System.out.println("コンピュータの勝ち!");27 }2829 scanner.close();30 }31}
課題2: ガチャシミュレーター
要件:
- 10回分のガチャを引く
- 各回で以下の確率でアイテムが出る:
- ノーマル: 70%
- レア: 25%
- 激レア: 5%
- 各レアリティの出現回数を集計して表示
ヒント:
int normal = 0, rare = 0, superRare = 0;
for (int i = 0; i < 10; i++) {
double prob = random.nextDouble();
if (prob < 0.7) {
normal++;
} else if (prob < 0.95) {
rare++;
} else {
superRare++;
}
}
期待される実行例:
ガチャ10回分の結果:
第1回: ノーマル
第2回: レア
第3回: ノーマル
...
第10回: ノーマル
【結果】
ノーマル: 7回
レア: 2回
激レア: 1回
解答例を見る
課題3: 数当てゲーム(拡張版)
要件:
- プログラムが1〜100のランダムな数を生成
- ユーザーが数を入力
- 「もっと大きい」「もっと小さい」「正解!」をヒントとして表示
- 正解するまで繰り返す
- 最後に試行回数を表示
ヒント:
int answer = random.nextInt(100) + 1; // 1〜100
int count = 0;
while (true) {
count++;
// 入力と判定
}
期待される実行例:
数当てゲーム(1〜100)
予想を入力 > 50
もっと大きい数です
予想を入力 > 75
もっと小さい数です
予想を入力 > 63
正解! 3回で当たりました!
解答例を見る
1import java.util.Random;2import java.util.Scanner;34public class Main {5 public static void main(String[] args) {6 Random random = new Random();7 Scanner scanner = new Scanner(System.in);89 int answer = random.nextInt(100) + 1; // 1〜10010 int count = 0;1112 System.out.println("数当てゲーム(1〜100)");1314 while (true) {15 System.out.print("予想を入力 > ");16 int guess = scanner.nextInt();17 count++;1819 if (guess == answer) {20 System.out.println("正解! " + count + "回で当たりました!");21 break;22 } else if (guess < answer) {23 System.out.println("もっと大きい数です\n");24 } else {25 System.out.println("もっと小さい数です\n");26 }27 }2829 scanner.close();30 }31}
FAQ
Q1: random.nextInt() と random.nextInt(10) の違いは?
A1:
nextInt():-2147483648 〜 2147483647 の 任意の整数nextInt(10):0 〜 9 の 範囲指定された整数
通常は nextInt(n) の方を使う場面が多い。
Q2: Math.random() とRandomクラスの違いは?
A2:
Javaには2つの乱数生成方法がある:
| 特徴 | Math.random() | Random |
|---|---|---|
| 型 | staticメソッド | クラス |
| 戻り値 | double (0.0〜1.0) | 様々な型 |
| 使いやすさ | シンプル | 高機能 |
| インスタンス | 不要 | 必要 |
Math.random() の例:
double r = Math.random(); // 0.0〜1.0
int dice = (int)(Math.random() * 6) + 1; // 1〜6
使い分け:
- Math.random():簡単な乱数が1つだけ必要な場合
- Random:複数の乱数、様々な型、シード制御が必要な場合
Q3: 毎回同じ乱数が出るのはなぜ?
A3:
以下のパターンで毎回同じ乱数が出ることがある:
パターン1: ループ内でRandomを再生成している
// ❌ 悪い例
for (int i = 0; i < 5; i++) {
Random random = new Random(); // ループ内で生成
System.out.println(random.nextInt(10));
}
短時間に複数のRandomインスタンスを作ると、同じシード(時刻)になる可能性がある。
解決策:Randomは1回だけ生成する
// ✅ 良い例
Random random = new Random(); // ループの外
for (int i = 0; i < 5; i++) {
System.out.println(random.nextInt(10));
}
パターン2: シードを固定している
Random random = new Random(12345); // 固定シード
デバッグ目的でない限り、シードは指定しない。
Q4: 1〜10の範囲を指定する正しい方法は?
A4:
間違った方法:
int number = random.nextInt(10); // 0〜9(10は含まれない)
正しい方法:
int number = random.nextInt(10) + 1; // 1〜10
一般化した公式:
int min = 最小値;
int max = 最大値;
int number = random.nextInt(max - min + 1) + min;
例(50〜100の範囲):
int number = random.nextInt(51) + 50; // 50〜100
Q5: 0.0〜100.0を含む小数を作る方法は?
A5:
nextDouble() だけでは上端の100.0を含められない。
100.0を含めたい場合は、刻み幅を決めて整数で生成してから小数に変換する 方法を使う。
例えば0.1刻みで0.0〜100.0を含めたいなら、以下のように書く:
double score = random.nextInt(1001) / 10.0; // 0.0〜100.0(100.0を含む)
nextInt(1001)は0〜1000を返す10.0で割ると0.0〜100.0になる- 1000が出たとき、
1000 / 10.0 = 100.0となる
Q6: 配列からランダムに選ぶ時の注意点は?
A6:
配列の長さを超えないように注意する。
間違った方法:
String[] items = {"A", "B", "C"};
int index = random.nextInt(4); // ❌ 0〜3(範囲外の可能性)
System.out.println(items[index]); // ArrayIndexOutOfBoundsException
正しい方法:
String[] items = {"A", "B", "C"};
int index = random.nextInt(items.length); // ✅ 0〜2
System.out.println(items[index]);
配列の長さ(items.length)を使う ことで、配列のサイズが変わっても安全。
まとめ
この章では、Javaで乱数を扱う方法を学んだ:
- Randomクラス:
java.util.Randomで乱数を生成 - 基本形:
Random random = new Random(); int n = random.nextInt(10); - 様々なメソッド:
nextInt(),nextDouble(),nextBoolean() - 範囲指定:
nextInt(max - min + 1) + minで任意の範囲 - シード:同じシードで同じ乱数列(デバッグに有用)
- 実践パターン:配列から選択、シャッフル、重み付き抽選
乱数は、ゲーム、シミュレーション、セキュリティなど、多くの場面で必要になる重要な技術である。
次章では、オブジェクト指向プログラミングの基礎を学び、より複雑なプログラムを設計する方法を身につける。