Знаешь, что бесит в программировании? Когда нужно что-то элементарное типа "дай мне случайное число от 1 до 10", а оказывается, что в Java для этого есть штук пять разных способов. И каждый со своими заморочками. Вот я недавно делал простенькую игру в кости, думал — ща быстренько Math.random() накину и всё. А потом начал копать глубже и понял, что есть варианты и получше. В общем, давай разбираться вместе, потому что эта тема реально полезная — случайные числа нужны везде. И в играх, и в тестах, и даже когда просто хочешь выбрать, что на обед съесть (шучу, но идея неплохая).Генерация случайного числа в заданном диапазоне - 1Начнём с 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
→ Счастливая семёрка!
На основе этого можно сделать полноценную настольную игру. Монополия, например, или что-нибудь своё придумать.

Что ещё важно знать

Про псевдослучайность

Помнишь я говорил "случайные числа"? Ну так вот, строго говоря они не совсем случайные. Это псевдослучайные числа — они вычисляются по хитрым математическим формулам, а не берутся из космического шума или радиоактивного распада. Для 99% задач это вообще не проблема. Игры, тесты, генерация данных — всё норм работает. Но если тебе нужна серьёзная криптография (генерация ключей, токены безопасности и всё такое), используй SecureRandom. Он медленнее, но по-настоящему случайный (ну, насколько это вообще возможно в компьютере).

Производительность имеет значение

Я как-то ради интереса прогнал бенчмарк на миллионе операций. Результаты примерно такие: • Math.random() — около 25 миллисекунд • Random — около 20 мс (чуть быстрее) • ThreadLocalRandom — около 15 мс Разница вроде небольшая, но в многопоточных условиях ThreadLocalRandom рвёт всех. Там уже не 15 мс, там в разы быстрее может быть.

Какой вариант выбрать?

Вот моя шпаргалка (сам по ней ориентируюсь): Учебная задача или простой скрипт? → Math.random(), не заморачивайся Обычное приложение, нужно больше контроля? → Random Много потоков или высокая нагрузка? → ThreadLocalRandom без вопросов Безопасность критична (пароли, ключи)? → SecureRandom Нужна воспроизводимость (тесты, дебаг)? → Random с seed

Подводим итоги

Вот мы и прошлись по всем основным способам генерации случайных чисел в Java. От простенького Math.random() до навороченного ThreadLocalRandom. Главное что нужно запомнить — не существует одного "правильного" способа. Всё зависит от задачи. Не надо стрелять из пушки по воробьям (юзать ThreadLocalRandom для простого калькулятора), но и не надо мучиться с тупым инструментом когда есть нормальный (тащить Math.random() в многопоточное приложение). Лично я обычно юзаю Random для большинства задач — золотая середина. Если вижу что тормозит — переключаюсь на ThreadLocalRandom. А Math.random() только для совсем простых скриптов. Теперь у тебя есть весь арсенал для работы со случайными числами. Можешь делать игры, симуляции, генераторы чего угодно. Экспериментируй, пробуй разные подходы, смотри что работает лучше в твоих проектах. И помни — случайность это не только полезная штука, но ещё и весёлая. Никогда не знаешь что выпадет в следующий раз :) Удачи в программировании!