Números aleatorios en Java explicados: Math.random(), Random, SecureRandom y patrones de rango

目次

1. Lo que aprenderás en este artículo

Cuando intentes trabajar con “números aleatorios” en Java, rápidamente te encontrarás con múltiples opciones como Math.random(), Random y SecureRandom.
Muchas personas terminan pensando: “¿Cuál se supone que debo usar?”

En esta sección, comenzaremos con lo esencial y aclararemos lo que podrás hacer al leer este artículo hasta el final. Al entender la visión general antes de profundizar en mecanismos detallados y código, las secciones posteriores serán mucho más fáciles de seguir.

1.1 Entenderás las principales formas de generar números aleatorios en Java

Este artículo explica las principales formas de generar números aleatorios en Java paso a paso.

Específicamente, aprenderás:

  • Un método simple que puedes usar de inmediato
  • Un método que permite un mayor control programático
  • Un método para situaciones donde la seguridad importa

Organizaremos todo por casos de uso para que sea fácil elegir el enfoque correcto.

Esto significa que este artículo funciona para ambos tipos de lectores:

  • Principiantes que quieren probar código de ejemplo
  • Personas que quieren ir más allá de “funciona, así que está bien”

La estructura está diseñada para apoyar a ambos tipos de lectores.

1.2 Entenderás las reglas de rango y las concepciones erróneas comunes

Uno de los mayores obstáculos al trabajar con números aleatorios es la selección de rango.

Por ejemplo:

  • Quieres un número aleatorio de 0 a 9
  • Quieres simular un lanzamiento de dado de 1 a 6
  • Quieres números aleatorios que incluyan valores negativos

En estos casos, surgen constantemente preguntas como las siguientes:

  • ¿Está incluido el límite superior?
  • ¿Está siempre incluido el límite inferior?
  • ¿Por qué no estoy obteniendo los valores que esperaba?

Este artículo lo explica en un flujo amigable para principiantes:
“¿Por qué sucede?” → “¿Cómo lo escribes correctamente?”

1.3 Aprenderás la diferencia entre reproducibilidad y riesgo

Los números aleatorios pueden tener requisitos opuestos dependiendo de la situación:

  • Quieres resultados diferentes cada vez
  • Quieres reproducir los mismos resultados repetidamente

Por ejemplo:

  • Para pruebas y depuración, quieres reproducir “los mismos valores aleatorios”
  • Para contraseñas y tokens, necesitas “valores aleatorios impredecibles”

Si los usas sin entender esta diferencia, puedes encontrarte con problemas como:

  • Pruebas inestables
  • Implementaciones peligrosas para la seguridad

Este artículo separa claramente:
“Aleatoriedad que debe ser reproducible” vs. “Aleatoriedad que no debe ser reproducible”

1.4 Podrás elegir “aleatoriedad segura” vs. “aleatoriedad peligrosa”

Java proporciona múltiples opciones para números aleatorios que pueden parecer similares, pero sus propósitos son completamente diferentes.

  • Aleatoriedad adecuada para juegos y código de ejemplo
  • Aleatoriedad que está bien para aplicaciones empresariales
  • Aleatoriedad que es absolutamente requerida para casos de uso de seguridad

Si los usas sin distinguir el propósito, es probable que termines con:

  • “Parece que funciona, pero en realidad es peligroso”
  • “Código que se convierte en un problema más adelante”

Este artículo explica los criterios de decisión con razones, en la forma:
“Para este caso de uso, usa esto.”

1.5 Incluso los principiantes podrán explicar “por qué esta es la forma correcta”

En lugar de solo listar ejemplos de código, nos enfocamos en el razonamiento detrás de ellos, como:

  • Por qué se usa este método
  • Por qué este estilo específico es correcto
  • Por qué otros enfoques no son adecuados

Enfatizamos el fondo y la forma de pensar.

Por lo tanto, esto es útil para personas que quieren:

  • Entenderlo y usarlo (no solo memorizarlo)
  • Alcanzar un nivel donde puedan explicarlo a otros

así como también.

2. Qué significan los “números aleatorios” en Java

Antes de generar números aleatorios en Java, esta sección organiza los fundamentos esenciales que debes conocer.
Si la omites y solo copias código, casi seguramente te confundirás más adelante.

2.1 “Aleatorio” no significa “perfectamente aleatorio”

Una idea errónea común entre los principiantes es la siguiente:
Los números aleatorios generados en Java no son “perfectamente aleatorios”.

La mayoría de los valores aleatorios usados en Java se describen con mayor precisión como:

  • Números calculados según reglas fijas (un algoritmo)
  • Parecen aleatorios, pero internamente siguen un patrón

Este tipo de aleatoriedad se llama número pseudoaleatorio (salida de PRNG).

2.2 ¿Qué es un Generador de Números Pseudoaleatorios (PRNG)?

Un generador de números pseudoaleatorios es un mecanismo que:

  • Parte de un valor inicial (una semilla)
  • Repite cálculos matemáticos
  • Produce una secuencia que parece aleatoria

Los beneficios principales incluyen:

  • Generación rápida
  • La misma semilla reproduce la misma secuencia aleatoria
  • Fácil de manejar en computadoras

Por otro lado, las desventajas incluyen:

  • Predecible si se conoce el algoritmo
  • Puede ser inadecuado para casos de uso de seguridad

2.3 Cuándo la “Aleatoriedad Reproducible” es Útil

A primera vista, podrías pensar:

Si aparecen los mismos valores aleatorios, ¿no es eso no aleatorio y, por tanto, malo?

Pero en la práctica, la aleatoriedad reproducible es extremadamente importante.

Por ejemplo:

  • Quieres verificar los mismos resultados cada vez en el código de pruebas
  • Quieres reproducir un informe de error
  • Quieres comparar resultados de simulaciones

En estas situaciones, es mucho más práctico tener:

  • Los mismos valores aleatorios en cada ejecución
  • en lugar de resultados que cambian cada vez

Muchas clases de números aleatorios de Java están diseñadas con esta reproducibilidad en mente.

2.4 Cuándo la Aleatoriedad NO debe ser Reproducible

Por otro lado, algunos valores aleatorios nunca deben ser reproducibles.

Ejemplos típicos incluyen:

  • Generación de contraseñas
  • Tokens de autenticación
  • IDs de sesión
  • Claves de un solo uso

Si estos valores son:

  • Predecibles
  • Reproducibles

eso por sí solo puede provocar un grave incidente de seguridad.

Por eso Java proporciona
generadores aleatorios enfocados en la seguridad
separados de los generadores pseudoaleatorios ordinarios.

Si implementas sin comprender esta diferencia, puedes terminar fácilmente con:

  • Código que “funciona” pero es peligroso
  • Código que se convierte en un problema en revisiones o auditorías

Así que asegúrate de entender este punto.

2.5 ¿Qué es una “Distribución Uniforme”? (Los conceptos básicos del sesgo)

Un término que aparece con frecuencia en discusiones sobre números aleatorios es distribución uniforme.

Una distribución uniforme significa:

  • Cada valor aparece con la misma probabilidad

Por ejemplo:

  • Un dado donde 1–6 son igualmente probables
  • Los dígitos 0–9 aparecen de forma uniforme

Ese estado es una distribución uniforme.

Las API aleatorias de Java están generalmente diseñadas asumiendo una distribución uniforme.

2.6 Ejemplos Comunes para Principiantes de “Aleatoriedad Sesgada”

Cuando ajustas manualmente los números aleatorios, puedes introducir sesgo sin querer.

Ejemplos comunes incluyen:

  • Forzar un rango usando % (el operador de resto)
  • Convertir (cast) double a int en el punto equivocado
  • No comprender si los límites son inclusivos

Estos son complicados porque el código sigue ejecutándose, lo que hace que el error
sea difícil de notar.

Secciones posteriores explicarán, con ejemplos concretos:

  • Por qué ocurre el sesgo
  • Cómo escribirlo correctamente

para que puedas evitar estas trampas.

3. Empezando Rápido con Math.random()

A partir de aquí, veremos formas concretas de generar números aleatorios en Java.
El primer método es Math.random(), que es el más sencillo y el que los principiantes encuentran con mayor frecuencia.

3.1 ¿Qué es Math.random()?

Math.random() es un método estático provisto por Java que devuelve un valor double aleatorio mayor o igual a 0.0 y menor que 1.0.

double value = Math.random();

Cuando ejecutas este código, el valor devuelto es:

  • mayor o igual a 0.0
  • menor que 1.0

En otras palabras, 1.0 nunca está incluido.

3.2 Por qué Math.random() es tan fácil de usar

La mayor ventaja de Math.random() es que
no requiere ninguna configuración.

  • Sin instanciación de clases
  • Sin declaraciones de importación
  • Utilizable en una sola línea

Debido a esto, es muy conveniente para:

  • Ejemplos de aprendizaje
  • Demostraciones simples
  • Verificar rápidamente el flujo del programa

en ese tipo de situaciones.

3.3 Generar números aleatorios enteros con Math.random()

En programas reales, a menudo querrás números aleatorios enteros
en lugar de valores double.

3.3.1 Generar un número aleatorio de 0 a 9

int value = (int)(Math.random() * 10);

Los valores producidos por este código son:

  • 0 o mayor
  • 9 o menor

He aquí por qué:

  • Math.random() devuelve valores de 0.0 a 0.999…
  • Multiplicar por 10 da 0.0 a 9.999…
  • Convertir a int trunca la parte decimal

3.4 Un error común al generar de 1 a 10

Un error muy común entre principiantes es dónde desplazar el valor inicial.

int value = (int)(Math.random() * 10) + 1;

Esto produce números aleatorios que son:

  • mayores o iguales a 1
  • menores o iguales a 10

Si inviertes el orden y escribes esto en su lugar:

// Common mistake
int value = (int)Math.random() * 10 + 1;

Este código da como resultado:

  • (int)Math.random() siempre es 0
  • El resultado siempre se convierte en 1

Siempre envuelve la conversión entre paréntesis—esto es un punto crítico.

3.5 Ventajas y limitaciones de Math.random()

Ventajas

  • Extremadamente simple
  • Bajo costo de aprendizaje
  • Suficiente para casos de uso pequeños y simples

Limitaciones

  • No reproducibilidad (sin control de semilla)
  • Sin control interno
  • No apto para casos de uso de seguridad
  • Carece de flexibilidad para escenarios complejos

En particular, si necesitas:

  • Los mismos valores aleatorios en pruebas
  • Control granular sobre el comportamiento

entonces Math.random() no será suficiente.

3.6 Cuándo deberías usar Math.random()

Math.random() es más adecuado para:

  • Aprendizaje inicial de Java
  • Código de explicación de algoritmos
  • Muestras de verificación simples

Por otro lado, para:

  • Aplicaciones empresariales
  • Código de pruebas
  • Lógica relacionada con seguridad

deberías elegir un generador de números aleatorios más apropiado.

4. Entendiendo la clase central java.util.Random

Ahora pasemos un paso más allá de Math.random() y veamos
la clase java.util.Random.

Random es una clase fundamental que se ha usado durante muchos años en Java. Aparece cuando deseas
“control adecuado sobre los números aleatorios.”

4.1 ¿Qué es la clase Random?

Random es una clase para generar números pseudoaleatorios.
La usas creando una instancia así:

import java.util.Random;

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

La mayor diferencia con Math.random() es que
el generador de números aleatorios se trata como un objeto.

Esto te permite:

  • Reutilizar el mismo generador
  • Mantener el comportamiento consistente
  • Gestionar explícitamente la aleatoriedad en tu diseño

lo cual no es posible con Math.random().

4.2 Tipos de valores aleatorios que puedes generar con Random

La clase Random proporciona métodos adaptados a diferentes casos de uso.

Ejemplos comunes incluyen:

  • nextInt() : un valor aleatorio int
  • nextInt(límite) : un int de 0 (inclusive) a límite (exclusivo)
  • nextLong() : un valor aleatorio long
  • nextDouble() : un double de 0.0 (inclusive) a 1.0 (exclusivo)
  • nextBoolean() : true o false

Al elegir el método adecuado, puedes generar valores aleatorios
adecuados de forma natural a cada tipo de dato.

5. Controlando el rango y la reproducibilidad con Random

Una de las mayores ventajas de usar java.util.Random es
el control explícito sobre rangos y reproducibilidad.

5.1 Generar valores dentro de un rango específico

El método más usado es nextInt(límite).

Random random = new Random();
int value = random.nextInt(10);

Este código produce valores que son:

  • mayores o iguales a 0
  • menores que 10

Así que el rango de resultados es 0 a 9.

5.2 Desplazando el rango (p.ej., de 1 a 10)

Para desplazar el rango, simplemente suma un desplazamiento:

int value = random.nextInt(10) + 1;

Esto produce valores desde:

  • 1 (inclusive)
  • 10 (inclusive)

Este patrón:

random.nextInt(range) + start

es la forma estándar de generar valores enteros aleatorios dentro de un rango en Java.

5.3 Generar Números Aleatorios con Rangos Negativos

También puedes generar rangos que incluyan valores negativos.

Por ejemplo, para generar valores de -5 a 5:

int value = random.nextInt(11) - 5;

Explicación:

  • nextInt(11) → 0 a 10
  • Restar 5 → -5 a 5

Este enfoque funciona de manera consistente sin importar el signo del rango.

5.4 Reproducibilidad con Semillas

Otra característica clave de Random es el control de semillas.

Si especificas una semilla, la secuencia aleatoria se vuelve reproducible:

Random random = new Random(12345);
int value1 = random.nextInt();
int value2 = random.nextInt();

Mientras se use el mismo valor de semilla:

  • Se genera la misma secuencia de valores aleatorios

Esto es extremadamente útil para:

  • Pruebas unitarias
  • Comparaciones de simulaciones
  • Depuración de errores difíciles

En contraste, Math.random() no permite controlar explícitamente la semilla.

6. Por Qué Random No Es Adecuado para Seguridad

En este punto, podrías pensar:

Random puede generar valores impredecibles, ¿entonces no sirve para seguridad?

Este es un malentendido muy común.

6.1 La Predictibilidad Es el Problema Central

java.util.Random utiliza un algoritmo determinista.

Esto significa:

  • Si se conoce el algoritmo
  • Si se observan suficientes valores de salida

entonces los valores futuros pueden ser predichos.

Para juegos o simulaciones, esto no es un problema.
Para seguridad, es una falla crítica.

6.2 Ejemplos Concretos de Uso Peligroso

Usar Random para lo siguiente es peligroso:

  • Generación de contraseñas
  • Claves de API
  • Identificadores de sesión
  • Tokens de autenticación

Aunque los valores “parezcan aleatorios”, no son criptográficamente seguros.

6.3 La Diferencia Clave Entre “Parecer Aleatorio” y “Seguro”

La distinción crítica es la siguiente:

  • Random: rápido, reproducible, predecible en teoría
  • Secure random: impredecible, resistente al análisis

La aleatoriedad orientada a la seguridad debe diseñarse bajo la suposición de que:

  • El atacante conoce el algoritmo
  • El atacante puede observar las salidas

Random no cumple con este requisito.

Por eso Java ofrece una clase separada específicamente para casos de uso de seguridad.

7. Usar SecureRandom para Aleatoriedad Crítica de Seguridad

Cuando la aleatoriedad debe ser impredecible y resistente a ataques,
Java proporciona java.security.SecureRandom.

Esta clase está diseñada específicamente para casos de uso sensibles a la seguridad.

7.1 Qué Hace a SecureRandom Diferente?

SecureRandom difiere de Random en sus objetivos de diseño.

  • Utiliza algoritmos criptográficamente fuertes
  • Obtiene entropía de múltiples fuentes del sistema
  • Está diseñada para ser impredecible incluso si se observan las salidas

A diferencia de Random, el estado interno de SecureRandom no es prácticamente reversible.

7.2 Uso Básico de SecureRandom

El uso es similar al de Random, pero la intención es muy distinta.

import java.security.SecureRandom;

SecureRandom secureRandom = new SecureRandom();
int value = secureRandom.nextInt(10);

Esto produce valores desde:

  • 0 (inclusive)
  • 10 (exclusivo)

La API es intencionalmente similar para que pueda reemplazar a Random cuando sea necesario.

7.3 Cuándo Debes Usar SecureRandom

Debes usar SecureRandom para:

  • Generación de contraseñas
  • IDs de sesión
  • Tokens de autenticación
  • Claves criptográficas

En estos escenarios, usar Random no es “un riesgo leve”, es incorrecto.

El costo de rendimiento de SecureRandom es intencional y aceptable para la seguridad.

8. APIs Aleatorias Modernas: ThreadLocalRandom y RandomGenerator

Las versiones recientes de Java ofrecen APIs aleatorias más avanzadas para abordar problemas de rendimiento y diseño.

8.1 ThreadLocalRandom: Aleatoriedad para Multihilos

ThreadLocalRandom está optimizado para entornos multihilo.

En lugar de compartir una única instancia de Random, cada hilo utiliza su propio generador.

int value = java.util.concurrent.ThreadLocalRandom.current().nextInt(1, 11);

Esto genera valores desde 1 (inclusive) hasta 11 (exclusive).

Las ventajas incluyen:

  • No hay contención entre hilos
  • Mejor rendimiento bajo concurrencia
  • APIs limpias basadas en rangos

Para procesamiento paralelo, esto suele ser preferible a Random.

8.2 RandomGenerator: Una Interfaz Unificada (Java 17+)

RandomGenerator es una interfaz introducida para unificar la generación de números aleatorios.

Permite:

  • Cambiar algoritmos fácilmente
  • Escribir código independiente de la implementación
  • Un diseño más preparado para el futuro
    import java.util.random.RandomGenerator;
    
    RandomGenerator generator = RandomGenerator.getDefault();
    int value = generator.nextInt(10);
    

Este enfoque se recomienda para bases de código Java modernas.

9. Resumen: Elegir la API de Aleatoriedad Adecuada en Java

Java ofrece múltiples APIs de generación de números aleatorios porque
“la aleatoriedad” tiene diferentes significados según el contexto.

9.1 Guía Rápida de Decisión

  • Math.random() : aprendizaje, demostraciones simples
  • Random : pruebas, simulaciones, comportamiento reproducible
  • ThreadLocalRandom : aplicaciones multihilo
  • RandomGenerator : diseño moderno y flexible
  • SecureRandom : contraseñas, tokens, seguridad

Elegir la equivocada puede no producir errores inmediatos,
pero puede generar problemas graves más adelante.

9.2 Principio Clave a Recordar

Lo más importante es lo siguiente:

La aleatoriedad es una decisión de diseño, no solo una llamada a función.

Al comprender la intención detrás de cada API, puedes escribir código Java que sea:

  • Correcto
  • Mantenible
  • Seguro

y adecuado para aplicaciones del mundo real.