Знаешь, что бесит в программировании? Когда нужно что-то элементарное типа "дай мне случайное число от 1 до 10", а оказывается, что в Java для этого есть штук пять разных способов. И каждый со своими заморочками.
Вот я недавно делал простенькую игру в кости, думал — ща быстренько Math.random() накину и всё. А потом начал копать глубже и понял, что есть варианты и получше. В общем, давай разбираться вместе, потому что эта тема реально полезная — случайные числа нужны везде. И в играх, и в тестах, и даже когда просто хочешь выбрать, что на обед съесть (шучу, но идея неплохая).
Начнём с Math.random() — проще некуда
Окей, самый базовый способ — это Math.random(). Его все знают, его все используют на первых порах. Просто потому что он есть, не нужно ничего импортировать, работает из коробки.
Что возвращает эта штука? Число типа double от 0.0 до... почти 1.0. Вот в этом вся засада — единицу ты никогда не получишь. Может быть 0.999999999, но ровно 1.0 — никогда. Запомни это, потом пригодится когда будешь диапазоны считать.
Давай сразу на примере:
Начнём с Math.random() — проще некуда
Окей, самый базовый способ — это Math.random(). Его все знают, его все используют на первых порах. Просто потому что он есть, не нужно ничего импортировать, работает из коробки.
Что возвращает эта штука? Число типа double от 0.0 до... почти 1.0. Вот в этом вся засада — единицу ты никогда не получишь. Может быть 0.999999999, но ровно 1.0 — никогда. Запомни это, потом пригодится когда будешь диапазоны считать.
Давай сразу на примере:
public class RandomExample {
public static void main(String[] args) {
// Просто умножаем на 100 и получаем вещественное от 0 до 100
double randomDouble = Math.random() * 100;
System.out.println("Вещественное: " + randomDouble);
// А если нужно целое — приводим к int и прибавляем 1 к диапазону
// Почему +1? Потому что Math.random() никогда не даст ровно 1.0
int randomInt = (int) (Math.random() * 101);
System.out.println("Целое от 0 до 100: " + randomInt);
}
}
Запустишь несколько раз — каждый раз разные числа:
Вещественное: 67.38291537398417
Целое от 0 до 100: 42
Ну вот, уже что-то. Но обычно же нужен не диапазон от нуля, правда? Чаще нужно что-то типа "от 10 до 75" или "от -50 до 50".
Для произвольного диапазона есть формула. Выглядит страшновато, но на самом деле логика простая:
public class RandomRange {
public static void main(String[] args) {
int min = 10;
int max = 75;
// Тут магия: вычитаем min, чтобы получить размер диапазона,
// прибавляем 1 (помнишь про то, что 1.0 никогда не выпадет?),
// потом сдвигаем обратно на min
int randomNumber = (int) (Math.random() * (max - min + 1)) + min;
System.out.println("Число от 10 до 75: " + randomNumber);
}
}
Работает:
Число от 10 до 75: 58
Честно говоря, когда я первый раз увидел эту формулу, минут пять тупил, пока не разобрался. Представь так: у тебя линейка от 0 до 1. Ты её растягиваешь на нужную длину (умножение), потом передвигаешь в нужное место (прибавление min). Всё, получилось.
Но есть нюанс — для простых задач Math.random() норм, но если у тебя многопоточное приложение или нужно генерить тысячи чисел подряд... это будет тормозить. Там внутри всякие синхронизации, блокировки. В общем, для серьёзных вещей лучше не использовать.
Класс Random — когда нужно чуть больше
А вот тут уже поинтереснее. Класс Random — это как полноценный инструмент по сравнению с Math.random(). Больше возможностей, больше контроля, и вообще удобнее работать. Импортируешь java.util.Random, создаёшь объект — и вперёд:
import java.util.Random;
public class RandomClass {
public static void main(String[] args) {
Random random = new Random();
// nextInt(100) вернёт число от 0 до 99. Верхняя граница не включается!
int randomInt = random.nextInt(100);
System.out.println("От 0 до 99: " + randomInt);
// А вот это фишка Java 17! Можно сразу указать оба края диапазона
int rangeInt = random.nextInt(10, 76); // от 10 до 75 включительно
System.out.println("От 10 до 75: " + rangeInt);
// Вещественное — как обычно, от 0.0 до 1.0
double randomDouble = random.nextDouble();
System.out.println("Double: " + randomDouble);
// А ещё есть boolean! Типа подброс монетки
boolean coinFlip = random.nextBoolean();
System.out.println("Монетка: " + (coinFlip ? "Орёл" : "Решка"));
}
}
Пример вывода:
От 0 до 99: 73
От 10 до 75: 42
Double: 0.3847592847592847
Монетка: Решка
Видишь эту штуку nextInt(10, 76)? Вот это я реально обожаю. Раньше приходилось городить формулы, а теперь просто пишешь два числа и всё. Правда, работает только начиная с Java 17, так что если у тебя старая версия — увы.А что с отрицательными числами?
Тут вообще красота. С Math.random() пришлось бы извращаться с формулами, а с Random всё элементарно:
import java.util.Random;
public class NegativeRange {
public static void main(String[] args) {
Random random = new Random();
// От -100 до 100 — просто пишем как есть
int negativeRange = random.nextInt(-100, 101);
System.out.println("От -100 до 100: " + negativeRange);
// Или вообще только отрицательные
int negativeOnly = random.nextInt(-50, -9);
System.out.println("От -50 до -10: " + negativeOnly);
}
}
Результат:
От -100 до 100: -37
От -50 до -10: -28
Никаких танцев с бубном. Работает как часы.Stream API — для массовой генерации
Окей, а теперь прикольная штука. Начиная с Java 8 можно генерировать целые потоки случайных чисел. Нужно 100 случайных чисел сразу? Легко!
import java.util.Random;
import java.util.Arrays;
public class RandomStreams {
public static void main(String[] args) {
Random random = new Random();
// Бац — и 10 случайных чисел от 1 до 100 в массиве
int[] numbers = random.ints(10, 1, 101).toArray();
System.out.println("10 чисел: " + Arrays.toString(numbers));
// Можно даже бесконечный поток создать (только limit не забудь!)
System.out.println("
Ещё 5 чисел:");
random.ints(1, 11)
.limit(5)
.forEach(n -> System.out.println("→ " + n));
}
}
Получится что-то типа:
10 чисел: [67, 23, 89, 45, 12, 78, 34, 91, 56, 8]
Ещё 5 чисел:
→ 7
→ 3
→ 9
→ 2
→ 5
Вот это я понимаю — удобно для тестовых данных! Нужно заполнить список из 1000 элементов случайными значениями? Одна строчка кода. Раньше приходилось циклы писать, а теперь всё элегантно.Seed — когда нужна "предсказуемая случайность"
Звучит как оксюморон, да? Но иногда нужны "случайные" числа, которые можно воспроизвести. Например, для тестирования — чтобы баг можно было повторить, или для демо-версии игры. Для этого есть seed (зерно). Если два генератора создать с одинаковым seed — они будут выдавать одинаковую последовательность:
import java.util.Random;
public class RandomSeed {
public static void main(String[] args) {
// Оба генератора с seed = 42
Random random1 = new Random(42);
Random random2 = new Random(42);
System.out.println("Первый генератор: " + random1.nextInt(100));
System.out.println("Второй генератор: " + random2.nextInt(100));
System.out.println("Снова первый: " + random1.nextInt(100));
System.out.println("Снова второй: " + random2.nextInt(100));
}
}
Смотри что получается:
Первый генератор: 81
Второй генератор: 81
Снова первый: 14
Снова второй: 14
Магия! Оба выдают одно и то же. Это как "записанная случайность" — числа вроде случайные, но предсказуемые. Если запустишь программу завтра — получишь те же самые числа, потому что seed одинаковый.
Я этим пользовался когда делал процедурную генерацию уровней в игре. Хранишь seed уровня — и можешь его пересоздать когда угодно, один в один.ThreadLocalRandom — когда скорость критична
Ладно, а теперь для тех кто любит оптимизации. Если твоё приложение многопоточное (а какое сейчас не многопоточное?), то обычный Random может стать узким горлом. Проблема в том, что Random использует один общий генератор для всех потоков. И когда несколько потоков пытаются одновременно получить случайное число — они встают в очередь. Представь супермаркет в пятницу вечером с одной работающей кассой. Вот такая же давка. ThreadLocalRandom решает проблему просто: у каждого потока свой личный генератор. Никакой очереди, никаких блокировок, полёт нормальный.
import java.util.concurrent.ThreadLocalRandom;
public class ThreadLocalExample {
public static void main(String[] args) {
// Главное отличие — не нужно создавать объект!
// Всегда используем current()
int randomInt = ThreadLocalRandom.current().nextInt(100);
System.out.println("От 0 до 99: " + randomInt);
// Диапазон тоже работает
int rangeInt = ThreadLocalRandom.current().nextInt(10, 76);
System.out.println("От 10 до 75: " + rangeInt);
double randomDouble = ThreadLocalRandom.current().nextDouble();
System.out.println("Double: " + randomDouble);
// Даже long поддерживает
long randomLong = ThreadLocalRandom.current().nextLong(1000, 10000);
System.out.println("Long от 1000 до 10000: " + randomLong);
}
}
Пример вывода:
От 0 до 99: 57
От 10 до 75: 33
Double: 0.7284372847283742
Long от 1000 до 10000: 5847
API почти такой же как у Random, но быстрее. Насколько быстрее? Ну, в многопоточных условиях разница может быть в разы. Я тестировал на веб-приложении — прирост производительности был ощутимый.
Единственный минус — нет seed. То есть воспроизвести последовательность не получится. Но для большинства задач это и не нужно.Практические примеры — применяем на деле
Теория — это хорошо, но давай уже код писать! Вот несколько реальных задач где случайные числа просто незаменимы.Генератор паролей
Классика жанра. Нужны надёжные пароли? Сделаем сами:
import java.util.concurrent.ThreadLocalRandom;
public class PasswordGenerator {
// Все символы которые можем использовать
private static final String CHARS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*";
public static String generate(int length) {
StringBuilder password = new StringBuilder();
ThreadLocalRandom random = ThreadLocalRandom.current();
// Просто берём случайные символы из строки
for (int i = 0; i < length; i++) {
int index = random.nextInt(CHARS.length());
password.append(CHARS.charAt(index));
}
return password.toString();
}
public static void main(String[] args) {
System.out.println("Пароль на 8 символов: " + generate(8));
System.out.println("Пароль на 12 символов: " + generate(12));
System.out.println("Пароль на 16 символов: " + generate(16));
}
}
Результат:
Пароль на 8 символов: kT3@mP9x
Пароль на 12 символов: Zq7&hR2@vL5!
Пароль на 16 символов: pN8$wK3@xT6#mR9%
Можно усложнить — добавить проверку что в пароле обязательно есть цифра, большая буква и спецсимвол. Но для базового варианта и так сойдётБросок игральных костей
Помнишь я говорил про игру в кости? Вот как это делается:
import java.util.Random;
public class DiceRoller {
private static final Random random = new Random();
public static int rollDice() {
// Кость от 1 до 6
return random.nextInt(1, 7);
}
public static void main(String[] args) {
System.out.println("Бросаем две кости:");
int dice1 = rollDice();
int dice2 = rollDice();
int sum = dice1 + dice2;
System.out.println("Первая кость: " + dice1);
System.out.println("Вторая кость: " + dice2);
System.out.println("Сумма: " + sum);
// Проверяем особые комбинации
if (sum == 7) {
System.out.println("→ Счастливая семёрка!");
} else if (sum == 12) {
System.out.println("→ Дубль шестёрок! Красава!");
} else if (dice1 == dice2) {
System.out.println("→ Дубль!");
}
}
}
Пример броска:
Бросаем две кости:
Первая кость: 4
Вторая кость: 3
Сумма: 7
→ Счастливая семёрка!
На основе этого можно сделать полноценную настольную игру. Монополия, например, или что-нибудь своё придумать.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ