こんにちは!侍エンジニア インストラクターの本多です。
Javaでランダムな値が欲しいと思ったことはありませんか?
例えば、おみくじアプリを作ろう!という時です。
人の意思や状況に依存せず、ランダムな値が欲しい!という時は「乱数」を使います。
この記事では、
・乱数とは何か?
・Math.Randomで乱数を生成する方法
・RandomクラスのnextIntメソッドの使い方
という基本的な内容から、
・シード(種)を指定して乱数を発生
・偏りなく乱数を生成する方法
・重複しない乱数を生成する方法
といった応用的な内容に関しても解説していきます。
今回は乱数について、さまざまな方法をわかりやすく解説します!
なお、Javaの記事については、こちらにまとめています。
乱数とは
乱数とは何が出るのかわからない、つまり何らかの規則や状況、人の意思に依存しないランダムな値のことです。
Javaで乱数を使用するには、Java.lang.Mathクラスのrandomメソッドを使う方法とjava.util.Randomクラスを使用するのが一般的です。
Mathクラスのrandomメソッドでは0.0から1.0未満のdouble型の乱数を取得します。
また、Randomクラスはint型やboolean型などさまざまな型で乱数を作ることができます。
Randomクラスでの乱数生成は、シード(種)と呼ばれる値をもとに擬似乱数が生成されます。
擬似乱数とは、プログラミングで生成する乱数に対して使われる用語です。
何が出るか先読みできない本当の意味での乱数と区別して使われます。
シードの値が同じであれば、ランダムな数値が生成されますが、同じ数値の羅列になります。
同じ数値の羅列を再現して出すことができるので本当の意味での乱数ではなく、区別して擬似乱数と呼ばれます。
それでは、乱数の生成について詳しく解説していきます。
Mathクラスのrandomメソッドの使い方
Java.lang.Mathクラスのrandomメソッドを使う方法についてみていきましょう。
randomメソッドは0.0から1.0未満のdouble型の乱数を生成するメソッドです。
public class RandomSample1 { public static void main(String[] args) { double randomNum1 = Math.random(); System.out.println(randomNum1); } }
実行結果:
0.13907073177448992
範囲を指定して乱数を発生させる方法
int型やlong型などさまざまな型の乱数が場合によっては必要なことがあります。
Mathクラスのrandomメソッドで生成したdouble型の値を、それぞれの型にキャストするなどの方法もありますが、もっと簡単に乱数を取得できる方法があります。
RandomクラスのnextIntメソッドなどを使う方法です。
nextIntメソッドを使うと、int型の乱数を生成することができます。
その他にもlong型やdouble型、float型、boolean型の乱数を生成するメソッドもあります。
それぞれの型を生成するメソッドについて以下の表にまとめました。
メソッド | 説明 |
---|---|
nextInt | int型の範囲内でint型の乱数を生成 引数にint型の値を指定すると、 0から指定値未満の範囲のint型の乱数を生成 |
nextLong | long型の範囲内でlong型の乱数を生成 |
nextDouble | 0.0から1.0未満の範囲でdouble型の乱数を生成 |
nextFloat | 0.0から1.0未満の範囲でfloat型の乱数を生成 |
nextBoolean | 真(true)または偽(false)型の乱数を生成 |
それでは、生成する値の範囲も指定できるnextIntメソッドの使い方について、サンプルコードで確認しましょう。
import java.util.Random; public class RandomSample2 { public static void main(String[] args) { Random rand = new Random(); int randomNumber = rand.nextInt(11); System.out.println(randomNumber); } }
実行結果:
8
このサンプルRandomSample2では、0から10までの整数を生成する、というものになります。
Randomクラスのインスタンスを生成し、それを格納したオブジェクトからnextIntメソッドを呼び出します。
シード(種)を指定して乱数を発生
さきほど、Randomクラスはシード(種)をもとに、擬似乱数を生成すると述べました。
シードを同じにすると同じ乱数の羅列が生成されます。
シードを指定するにはRandomクラスのインスタンス生成時にコンストラクタの引数をlong型で指定します。
また、setSeedメソッドを使って引数をlong型で指定することでもシードを設定することができます。
サンプルコードで確認しましょう。
import java.util.Arrays; import java.util.Random; public class RandomSample3 { public static void main(String[] args) { int[] nums = new int[5]; // 1回目のランダム値の羅列 Random rand1 = new Random(8); for(int i = 0; i < 5; i++) { nums[i] = rand1.nextInt(); } System.out.println(Arrays.toString(nums)); // 2回目のランダム値の羅列 Random rand2 = new Random(8); for(int i = 0; i < 5; i++) { nums[i] = rand2.nextInt(); } System.out.println(Arrays.toString(nums)); } }
実行結果:
[-1158562568, -70013384, -1731337436, 64862043, 858120744] [-1158562568, -70013384, -1731337436, 64862043, 858120744]
このサンプルコードでは、ランダム値の羅列を2回行っています。
2回とも、Randomクラスのインスタンス生成時に同じ値のシードでランダム値を羅列しています。
実行結果を確認すると同じ値が羅列されていることがわかります。
なお、Randomクラスのインスタンス生成時に引数を指定しない場合は、シードには実行時の現在時刻を使用しています。
偏りなく乱数を生成する方法
前章では、Randomクラスを使ってランダムな値を生成する方法について解説してきました。
しかし、Randomクラスを使って生成したランダム値には偏りがあるといわれています。
シードに現在時刻を続けて使って生成した場合や、連続した値を指定して生成した場合は特にです。
それに対して、java.security.SecureRandomクラスを使うことで偏りを減らすことができます。
SecureRandomクラスはシードの素材が予測不可能であることが特徴です。
Randomクラスを使った場合とSecureRandomクラスを使った場合で比較してみましょう。
import java.security.SecureRandom; import java.util.Random; public class RandomSample4 { public static void main(String[] args) { float min = Float.MAX_VALUE; float max = Float.MIN_VALUE; // Randomクラスを使用 Random rand1 = new Random(); for(int i = 0; i < 1000; i++) { rand1.setSeed(i); float x = rand1.nextFloat(); if(x > max) max = x; if(x < min) min = x; } System.out.println("min= " + min + ", max= " + max); min = Float.MAX_VALUE; max = Float.MIN_VALUE; // SecureRandomクラスを使用 Random rand2 = new SecureRandom(); for(int i = 0; i < 1000; i++) { rand2.setSeed(i); float x = rand1.nextFloat(); if(x > max) max = x; if(x < min) min = x; } System.out.println("min= " + min + ", max= " + max); } }
実行結果:
min= 0.67533773, max= 0.76697946 min= 0.002446413, max= 0.99947137
このサンプルコードでは、RandomクラスとSecureRandomクラスを使って乱数を生成しています。
どちらの場合も、シードには連続した値を使用して生成した値の最小値と最大値を出力表示しています。
Randomクラスの場合、最小値と最大値の値が近く偏りがあることがわかります。
これに対しては、SecureRandomクラスは0.0から1.0未満の範囲内で乱数が生成されていることがわかります。
このようにシード(種)の偏りを減らして乱数を生成したい場合は、SecureRandomクラスを使用することをオススメします。
重複しない乱数を生成する方法
乱数の発生といっても、同じ値が全く出ないという保証はありません。
何度かは同じ値が生成されることがあります。
単純に発生させるだけでは重複は避けられません。
重複しない乱数、例えば0から10までの整数を重複なく得るにはどうすればよいでしょうか?
ここでは0から10までの整数を順番にArrayListに格納し、shuffleメソッドを使ってシャッフルし、その結果を取り出すという方法をご紹介します。
import java.util.ArrayList; import java.util.Collections; public class RandomSample5 { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<Integer>(); // listに値を入れる。この段階では昇順 for(int i = 0 ; i <= 10 ; i++) { list.add(i); } // シャッフルして、順番を変える Collections.shuffle(list); // シャッフルした結果を表示させる System.out.println(list); } }
実行結果:
[10, 8, 1, 3, 9, 6, 7, 4, 5, 2, 0]
単純に順番に表示させただけですが、順番に取り出していけば重複しない値を得ることができます。
実行するたびに、順番が変わって出力表示されます。
ランダムな文字列を生成する方法
Javaでランダムな文字列を生成するためのクラスが、org.apache.commons.lang3には用意されているようです。
RandomStringUtilsクラスです。
randomNumericメソッドを使うと数値の文字列を、randomAlphabeticメソッドを使うとアルファベットの文字列を、randomAsciiメソッドを使うとアスキー文字の文字列を生成することができます。
詳細はこちらのサイトを参照してください。
https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/RandomStringUtils.html
まとめ
この記事では乱数を生成する方法を解説しました。
単純に乱数を発生させる、といってもいろいろな方法がありますね。
実数か整数か、どこからどこまでの範囲か、重複を許すかどうか、などなど、、、
もし、乱数を生成させる方法を忘れてしまったら、この記事を思い出してくださいね!