Почему код со случайными строками выводит "hello world"?
Короткий ответ: seed (зерно) генератора случайных чисел
Класс java.util.Random при создании с одинаковым seed (передаётся в конструктор) всегда генерирует одну и ту же последовательность псевдослучайных чисел. Числа -229985452 и -147909649 были специально подобраны так, чтобы первые пять вызовов nextInt(26) дали коды букв "hello" и "world".
Как это работает?
Генератор Random использует линейный конгруэнтный метод (LCG). Если вы передаёте одинаковое начальное значение (seed), то последовательность чисел будет строго детерминированной:
Random r = new Random(42);
System.out.println(r.nextInt()); // Всегда одно и то же число
System.out.println(r.nextInt()); // Всегда следующее предсказуемое число
Автор вопроса подобрал seed методом перебора. Он написал программу, которая перебирает разные seed, пока не найдёт такие, которые дают нужные последовательности букв:
public class FindSeed {
public static void main(String[] args) {
String target = "hello";
long seed = Long.MIN_VALUE;
while (seed < Long.MAX_VALUE) {
Random r = new Random(seed);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
sb.append((char) ('a' + r.nextInt(26)));
}
if (sb.toString().equals(target)) {
System.out.println("Найден seed: " + seed);
break;
}
seed++;
}
}
}
Этот перебор может занять некоторое время (минуты или часы), но для коротких строк он вполне выполним. Для "hello" seed = -229985452, для "world" — -147909649.
Почему это не "случайность" в обычном смысле?
- Псевдослучайность — числа выглядят случайными, но полностью определяются seed.
- Детерминированность — одинаковый seed → одинаковый результат на всех JVM и платформах.
- Обратимость — зная алгоритм, можно подобрать seed под нужную последовательность.
Как избежать такой "магии"?
Если вам нужна настоящая непредсказуемость, используйте SecureRandom или не указывайте seed явно:
Random r = new Random(); // seed = текущее время + счётчик, непредсказуем
Но помните: если вы задаёте seed явно (например, для воспроизводимости тестов), поведение будет полностью детерминированным. Это часто используется в играх (одинаковые карты при одном seed) или в тестировании.
Так что ваш код — не магия, а просто удачно подобранные seed. Кстати, можете проверить: если заменить seed на new Random().nextLong() и запустить, то строки будут разными при каждом запуске.
Альтернативный взгляд: использование хеш-функций
Интересно, что эту же идею можно реализовать через хеширование. Например, подобрать seed для Random можно через хеш-функцию от строки, но это сложнее. Есть более простой способ: использовать String.hashCode() и обратную инженерию.
// Получаем seed из хеша строки
String word = "hello";
int hash = word.hashCode(); // 99162322
// Теперь ищем seed, который даст первые 5 чисел = буквы "hello"
// Это обратная задача — перебор seed по хешу
Но проще всего запомнить: Random с одинаковым seed — это не случайность, а детерминированная функция. Если вы видите в коде явное число в конструкторе Random, знайте — это не баг, а фича для воспроизводимости.
Кстати, на Stack Overflow этот вопрос стал знаменитым именно из-за элегантности подбора seed. Некоторые разработчики даже шутят, что это "пасхалка" от автора JDK, но на самом деле это просто результат перебора.
Совет: если хотите удивить коллег, можете использовать этот трюк в демонстрациях или тестах. Но в продакшене лучше не полагаться на магические числа — используйте константы или конфигурацию.