Перейти к содержанию

Генерация случайных чисел в Java

В этой статье рассмотрим различные способы генерации случайных чисел в Java.

Использование Java API

Java API предоставляет несколько способов достижения нашей цели. Давайте посмотрим на некоторые из них.

java.lang.Math

Метод random класса Math вернет значение double в диапазоне от 0,0 (включительно) до 1,0 (не включая). Давайте посмотрим, как использовать его для получения случайного числа в заданном диапазоне, определяемом минимумом и максимумом:

int randomWithMathRandom = (int) ((Math.random() * (max - min)) + min);

java.util.Random

До Java 1.7 самым популярным способом генерации случайных чисел было использование nextInt. Было два способа использования этого метода, с параметрами и без них. Вызов без параметров возвращает любое из значений int с примерно равной вероятностью. Очень вероятно, что мы получим отрицательные числа:

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

Если используем вызов netxInt с привязанным параметром, то получим числа в пределах диапазона:

int randomWintNextIntWithinARange = random.nextInt(max - min) + min;

В итоге получи число от 0 (включительно) до параметра (исключительно). Таким образом, связанный параметр должен быть больше 0. В противном случае получим исключение java.lang.IllegalArgumentException.

В Java 8 появились новые методы ints, которые возвращают java.util.stream.IntStream. Давайте посмотрим, как их использовать. Метод ints без параметров возвращает неограниченный поток значений int:

IntStream unlimitedIntStream = random.ints();

Можно передать один параметр, чтобы ограничить размер потока:

IntStream limitedIntStream = random.ints(streamSize);

И, конечно же, можно установить максимум и минимум для генерируемого диапазона:

IntStream limitedIntStreamWithinARange = random.ints(streamSize, min, max);

java.util.concurrent.ThreadLocalRandom

В Java 1.7 появился новый и более эффективный способ генерации случайных чисел с помощью класса ThreadLocalRandom. Он имеет три важных отличия от класса Random:

  • Не нужно явно инициировать новый экземпляр ThreadLocalRandom. Это помогает избежать ошибок, связанных с созданием множества бесполезных экземпляров и тратой времени на сборщик мусора.
  • Нельзя установить начало для ThreadLocalRandom, что может привести к реальной проблеме. Если нужно установить начальное число, следует избегать этого способа генерации случайных чисел.
  • Класс Random плохо работает в многопоточных средах.

Теперь давайте посмотрим, как это работает:

int randomWithThreadLocalRandomInARange = ThreadLocalRandom.current().nextInt(min, max);

Начиная с Java 8 появились новые возможности. Во-первых, есть два варианта метода nextInt:

int randomWithThreadLocalRandom = ThreadLocalRandom.current().nextInt();
int randomWithThreadLocalRandomFromZero = ThreadLocalRandom.current().nextInt(max);

Во-вторых, что более важно, можно использовать метод ints:

IntStream streamWithThreadLocalRandom = ThreadLocalRandom.current().ints();

java.util.SplittableRandom

В Java 8 также появился очень быстрый генератор – класс SplittableRandom.

Как видно из JavaDoc, это генератор для использования в параллельных вычислениях. Важно знать, что экземпляры не потокобезопасны. Поэтому нужно соблюдать осторожность при использовании этого класса.

Есть методы nextInt и ints. С помощью nextInt можно напрямую установить верхний и нижний диапазон, используя вызов двух параметров:

SplittableRandom splittableRandom = new SplittableRandom();
int randomWithSplittableRandom = splittableRandom.nextInt(min, max);

Этот способ использования проверяет, что максимальный параметр больше минимального. В противном случае получим исключение IllegalArgumentException. Однако он не проверяет, работаем ли с положительными или отрицательными числами. Таким образом, любой из параметров может быть отрицательным. Кроме того, есть доступные вызовы с одним и нулевым параметром. Они работают так же, как описано ранее.

Также есть методы ints. Это означает, что можно легко получить поток значений int. Чтобы уточнить, можно выбрать ограниченный или неограниченный поток. Для ограниченного потока можно установить верх и низ для диапазона генерации чисел:

IntStream limitedIntStreamWithinARangeWithSplittableRandom = splittableRandom.ints(streamSize, min, max);

java.security.SecureRandom

Если есть приложения, чувствительные к безопасности, необходимо рассмотреть возможность использования SecureRandom. Это криптографически стойкий генератор. Экземпляры, созданные по умолчанию, не используют криптографически случайные начальные значения. Итак, необходимо либо:

  • установить начало – следовательно, начальное значение будет непредсказуемым;
  • установить для системного свойства java.util.secureRandomSeed значение true.

Этот класс наследуется от java.util.Random. Поэтому доступны все способы, которые рассмотрели выше. Например, если нужно получить любое из значений int, то вызовем nextInt без параметров:

SecureRandom secureRandom = new SecureRandom();
int randomWithSecureRandom = secureRandom.nextInt();

С другой стороны, если нужно установить диапазон, можно вызвать его с привязанным параметром:

int randomWithSecureRandomWithinARange = secureRandom.nextInt(max - min) + min;

Необходимо помнить, что этот способ использования генерирует исключение IllegalArgumentException, если параметр не больше нуля.

Использование сторонних API

Java предоставляет множество классов и методов для генерации случайных чисел. Однако для этой цели также существуют сторонние API. Посмотрим на некоторые из них.

org.apache.commons.math3.random.RandomDataGenerator

В математической библиотеке commons из проекта Apache Commons есть много генераторов. Самый простой и, вероятно, самый полезный – RandomDataGenerator. Он использует алгоритм Well19937c для случайной генерации. Однако можно предоставить реализацию своего алгоритма. Давайте посмотрим, как его использовать. Во-первых, необходимо добавить зависимость:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-math3</artifactId>
    <version>3.6.1</version>
</dependency>

Последнюю версию commons-math3 можно найти на Maven Central. Затем можно начать работать с ней:

RandomDataGenerator randomDataGenerator = new RandomDataGenerator();
int randomWithRandomDataGenerator = randomDataGenerator.nextInt(min, max);

it.unimi.dsi.util.XoRoShiRo128PlusRandom

Безусловно, это одна из самых быстрых реализаций генератора случайных чисел. Он был разработан на факультете информационных наук Миланского университета. Библиотека также доступна в репозиториях Maven Central. Итак, добавим зависимость:

<dependency>
    <groupId>it.unimi.dsi</groupId>
    <artifactId>dsiutils</artifactId>
    <version>2.6.0</version>
</dependency>

Этот генератор наследуется от java.util.Random. Однако, если посмотрим на JavaDoc, то поймем, что есть только один способ его использования – через метод nextInt. Прежде всего, этот метод доступен только для вызовов с нулевым и одним параметром. Любые другие вызовы будут напрямую использовать методы java.util.Random. Например, если хотим получить случайное число в диапазоне, то должны написать:

XoRoShiRo128PlusRandom xoroRandom = new XoRoShiRo128PlusRandom();
int randomWithXoRoShiRo128PlusRandom = xoroRandom.nextInt(max - min) + min;

Заключение

Существует несколько способов реализации генерации случайных чисел. Однако лучшего способа не существует. Следовательно, необходимо выбирать тот, который лучше всего соответствует потребностям.

Полный пример можно найти на GitHub.

Оригинал