Saltar a contenido

UD2 · Sección 5 — La clase Random y ThreadLocalRandom

Dato Valor
Unidad Didáctica UD2 — Programación Orientada a Objetos con la API de Java
Resultado de Aprendizaje RA2 — Escribe y prueba programas sencillos, reconociendo y aplicando los fundamentos de la POO orientada a objetos.
Sección 5 de 10 — La clase Random y ThreadLocalRandom
Evaluación 1.ª Evaluación
Duración estimada 2 horas lectivas
Criterios de evaluación cubiertos CE 2.2 (escribe programas simples), CE 2.3 (instancia objetos a partir de clases predefinidas), CE 2.4 (utiliza métodos y propiedades), CE 2.5 (llamadas a métodos estáticos), CE 2.6 (utiliza parámetros), CE 2.8 (utiliza constructores)
Prerrequisitos UD1 completa, UD2 Secciones 1, 2, 3 y 4
Paquete del código de ejemplo dam.programacion.ud2
Herramientas JDK 25, IntelliJ IDEA Community, Maven 3.9.x

1. Punto de partida

En la sección anterior conociste Math.random(), una forma rápida de generar números aleatorios en el rango [0.0, 1.0). Es cómoda pero limitada: no puedes generar directamente enteros, booleanos o long; no puedes reproducir secuencias; y usa internamente un único Random compartido por toda la aplicación, lo que puede ser un cuello de botella si muchos hilos lo usan a la vez.

En esta sección vas a conocer dos clases que resuelven esas limitaciones: Random (la clase clásica, plenamente funcional) y ThreadLocalRandom (la versión moderna y recomendada para la mayoría de casos). Ambas viven en java.util y comparten la misma API, así que aprender una es aprender casi la otra.

Al terminar la sección serás capaz de: crear un generador de aleatorios con y sin semilla, generar valores de distintos tipos (enteros, decimales, booleanos, long), entender por qué la reproducibilidad es importante en tests y depuración, y elegir correctamente entre Random y ThreadLocalRandom.


2. ¿Qué es un número "aleatorio" para una computadora?

Antes de usar la clase, conviene aclarar una idea importante: una computadora no genera números verdaderamente aleatorios (salvo que use hardware especializado). Lo que genera son números pseudoaleatorios: secuencias que parecen aleatorias (pasan tests estadísticos) pero en realidad están calculadas a partir de una fórmula matemática determinista.

La fórmula parte de un valor inicial llamado semilla (seed). A partir de la semilla, el generador produce una secuencia que, mientras la fórmula sea buena, parece impredecible. Si conoces la semilla y la fórmula, puedes reproducir exactamente la misma secuencia. Esto tiene una consecuencia práctica importantísima que veremos enseguida: la reproducibilidad.

Analogía de la receta. Imagina que "aleatorio" fuera una receta de cocina que produce un plato distinto cada vez, pero la receta se prepara a partir de un ingrediente inicial (la semilla). Si dos cocineros parten del mismo ingrediente inicial, ambos obtienen exactamente la misma secuencia de platos, en el mismo orden. Si parten de ingredientes distintos, los platos son diferentes. La "apariencia aleatoria" viene de que la receta mezcla los sabores de forma compleja.

Use mouse to pan and zoom
flowchart LR
    S1["Semilla 42"] --> R["Formula matematica<br/>(PRNG)"]
    S2["Semilla 99"] --> R2["Formula matematica<br/>(PRNG)"]
    R --> O1["Secuencia 1<br/>7, 19, 3, 88, ..."]
    R2 --> O2["Secuencia 2<br/>51, 2, 14, 73, ..."]

    S3["Misma semilla 42"] --> R3["Misma formula"]
    R3 --> O3["Misma secuencia 1<br/>7, 19, 3, 88, ..."]

3. La clase Random

3.1. Import y primer ejemplo

Random vive en java.util, así que necesita import:

import java.util.Random;

Creación de un generador y generación de valores básicos:

package dam.programacion.ud2;

import java.util.Random;

public class PrimerRandom {
    public static void main(String[] args) {
        Random generador = new Random();

        System.out.println(generador.nextInt(6) + 1);  // entero entre 1 y 6
        System.out.println(generador.nextDouble());     // double en [0.0, 1.0)
        System.out.println(generador.nextBoolean());    // true o false
        System.out.println(generador.nextLong());       // long aleatorio
    }
}

Cada vez que ejecutes el programa, los valores serán distintos (salvo que uses semilla, lo veremos enseguida).

3.2. Constructores: con y sin semilla

Random tiene dos constructores, lo que encaja perfectamente con el CE 2.8 (utiliza constructores):

Constructor Qué hace Cuándo usarlo
Random() Crea un generador con semilla "aleatoria" (basada en el reloj del sistema) Caso general: programas de producción, juegos, simulaciones
Random(long semilla) Crea un generador con una semilla fija Tests, depuración, simulaciones reproducibles

Sin semilla: secuencia distinta cada ejecución

1
2
3
Random g = new Random();
System.out.println(g.nextInt(100));   // p.ej. 42
// Si ejecutas de nuevo el programa, sale otro numero distinto

Internamente, new Random() usa como semilla una combinación del tiempo actual en nanosegundos y unos valores internos de la JVM. Por eso dos ejecuciones dan resultados distintos.

Con semilla: secuencia reproducible

1
2
3
4
5
6
7
8
Random g1 = new Random(42);
Random g2 = new Random(42);

System.out.println(g1.nextInt(100));   // 42
System.out.println(g2.nextInt(100));   // 42 (¡exactamente igual!)

System.out.println(g1.nextInt(100));   // 89
System.out.println(g2.nextInt(100));   // 89

Dos objetos Random creados con la misma semilla producen exactamente la misma secuencia. Esto es valiosísimo para:

  • Tests automáticos: si un test genera datos aleatorios y falla, poder reproducir la misma secuencia te permite depurar.
  • Depuración: un bug que aparece "a veces" se puede reproducir siempre si guardas la semilla.
  • Simulaciones científicas: dos investigadores deben poder repetir el mismo experimento.
  • Juegos competitivos: en juegos por turnos, compartir la semilla permite a ambos jugadores ver el mismo "tablero aleatorio".

Analogía de la lista de reproducción. Imagina que un Random es una lista de reproducción infinita de números. La semilla es el "código de la lista": si dos personas introducen el mismo código, obtienen exactamente la misma lista en el mismo orden. Si introducen códigos distintos, las listas son diferentes.

3.3. Tabla de métodos principales

Método Qué devuelve Rango
nextInt() int Todo el rango de int (positivos y negativos)
nextInt(int bound) int [0, bound) — bound es exclusivo
nextLong() long Todo el rango de long
nextDouble() double [0.0, 1.0)
nextFloat() float [0.0, 1.0)
nextBoolean() boolean true o false (50/50)
nextGaussian() double Distribución normal (media 0, desviación 1)
ints(int cantidad, int min, int max) IntStream Stream de enteros (adelanto de UD6)
setSeed(long semilla) void Cambia la semilla del generador

Ejemplos:

Random g = new Random();

// Enteros en un rango
System.out.println(g.nextInt(10));          // 0..9
System.out.println(g.nextInt(6) + 1);       // 1..6 (dado)
System.out.println(g.nextInt(100, 200));    // 100..199 (Java 17+)
System.out.println(g.nextInt());            // cualquier int (positivo o negativo)

// Decimales
System.out.println(g.nextDouble());         // 0.0..0.999...
System.out.println(g.nextDouble(5.0, 10.0)); // 5.0..9.999... (Java 17+)

// Booleanos y long
System.out.println(g.nextBoolean());        // true o false
System.out.println(g.nextLong());           // cualquier long

// Distribucion normal (gaussiana)
System.out.println(g.nextGaussian());       // p.ej. -0.342, 1.12, 0.05, ...

Atención: nextInt(int bound) requiere que bound sea positivo. Si le pasas 0 o un negativo, lanza IllegalArgumentException.

3.4. Generar enteros en un rango [min, max] (inclusivo)

El patrón más común. Hay dos formas, dependiendo de la versión de Java:

Forma clásica (todas las versiones)

1
2
3
4
5
// Entero entre 1 y 6 (dados)
int dado = g.nextInt(6) + 1;

// Entero entre 10 y 50
int n = g.nextInt(41) + 10;   // 0..40 + 10 = 10..50

La fórmula general es:

int aleatorio = g.nextInt(max - min + 1) + min;

Donde max - min + 1 es el número de valores posibles en el rango [min, max] (ambos inclusive).

Forma moderna (Java 17+)

Desde Java 17, nextInt admite dos argumentos:

int dado = g.nextInt(1, 7);          // 1..6 (origen inclusivo, bound exclusivo)
int n = g.nextInt(10, 51);           // 10..50

Es más legible pero requiere recordar que el segundo argumento es exclusivo. Por eso nextInt(1, 7) da 1..6, no 1..7.

3.5. Distribución uniforme vs. gaussiana

  • nextInt, nextDouble: distribución uniforme. Cada valor dentro del rango tiene la misma probabilidad de salir.
  • nextGaussian: distribución normal (campana de Gauss). Los valores cercanos a 0 son mucho más probables que los valores grandes. Útil para modelar alturas, tiempos, errores de medición.
Random g = new Random();

// Cinco tiros uniformes en [0, 100)
for (int i = 0; i < 5; i++) {
    System.out.print(g.nextInt(100) + " ");
}
// p.ej.: 23 87 4 51 92  (cada valor igual de probable)

// Cinco valores gaussianos
for (int i = 0; i < 5; i++) {
    System.out.printf("%.2f  ", g.nextGaussian());
}
// p.ej.: -0.34  1.12  0.05  -1.45  0.78  (casi siempre cerca de 0)

Analogía de los dados vs. la altura. Tirar un dado de 6 caras es distribución uniforme: cada cara sale con probabilidad 1/6. En cambio, la altura de las personas es gaussiana: la mayoría mide entre 1,60 y 1,80, pocas personas miden 1,50 o 1,90, y casi nadie mide 1,40 o 2,00.

3.6. Random.ints(...): adelanto de Streams

El método ints devuelve un IntStream, que es un flujo de enteros. Es un adelanto de la Streams API que se verá a fondo en la UD6. Aquí lo mencionamos para que lo reconozcas si lo ves:

1
2
3
4
5
6
7
import java.util.Random;
import java.util.stream.IntStream;

// Cinco enteros aleatorios en [0, 100)
new Random().ints(5, 0, 100)
    .forEach(n -> System.out.print(n + " "));
// p.ej.: 23 87 4 51 92

No te preocupes por entenderlo ahora; lo retomarás en la UD6.


4. ThreadLocalRandom: la mejor práctica moderna

4.1. El problema de Random en concurrencia

Random es seguro para usar desde varios hilos a la vez (es thread-safe), pero internamente usa una variable compartida protegida con sincronización. Si muchos hilos llaman a nextInt simultáneamente, hay contención y el rendimiento cae.

Para entender por qué, imagina una类比 simple:

Analogía del grifo compartido. Imagina un único grifo en un bar concurrido. Cada vez que alguien quiere agua, hace cola. El agua sale correctamente para todos, pero la cola hace que el proceso sea lento. Random es ese grifo: funciona, pero si 50 hilos quieren generar aleatorios a la vez, hacen cola.

4.2. La solución: ThreadLocalRandom

ThreadLocalRandom (introducido en Java 7) da a cada hilo su propio generador independiente. No hay sincronización ni contención: cada hilo tiene su propio "grifo".

Analogía del grifo por mesa. Imagina ahora que cada mesa del bar tiene su propio grifo. Los clientes de cada mesa no hacen cola: cada uno bebe de su grifo. Si hay 50 mesas, 50 personas pueden beber simultáneamente sin esperar.

4.3. Cómo se usa

A diferencia de Random, no se instancia con new. Se obtiene con el método estático ThreadLocalRandom.current():

import java.util.concurrent.ThreadLocalRandom;

public class EjemploThreadLocalRandom {
    public static void main(String[] args) {
        int dado = ThreadLocalRandom.current().nextInt(1, 7);   // 1..6
        double d = ThreadLocalRandom.current().nextDouble();    // [0.0, 1.0)
        boolean b = ThreadLocalRandom.current().nextBoolean();

        System.out.println(dado + " " + d + " " + b);
    }
}

4.4. Diferencias clave con Random

Aspecto Random ThreadLocalRandom
Creación new Random() o new Random(semilla) ThreadLocalRandom.current()
Constructor con semilla Sí, para reproducibilidad No (no permite semilla explícita)
Concurrencia Seguro pero con contención Un generador por hilo, sin contención
Rendimiento en multihilo Bajo Alto
Rendimiento en un solo hilo Bueno Ligeramente mejor
Métodos disponibles Casi idénticos Casi idénticos, más sobrecargas
Recomendación Para tests, depuración o cuando necesites reproducibilidad Para producción, especialmente con múltiples hilos

4.5. Cuándo usar cada uno

Situación Recomendación
Programa de un solo hilo, sin requisitos especiales ThreadLocalRandom.current() (es ligeramente más rápido y la API es más cómoda)
Programa multihilo (servidores, juegos en red) ThreadLocalRandom.current() (sin contención)
Tests automáticos donde necesitas reproducir una secuencia new Random(semilla)
Depuración de un bug que depende de valores aleatorios new Random(semilla) con la semilla que falló
Simulación científica reproducible new Random(semilla)
Caso general sin requisitos ThreadLocalRandom.current()

Regla práctica para este curso: en ejercicios de clase donde necesites reproducibilidad (tests, ejemplos que tienen que dar la misma salida), usa new Random(semilla). En el resto, usa ThreadLocalRandom.current().


5. Caso especial: la clase SecureRandom

Existe una tercera clase para generar aleatorios: SecureRandom (en java.security). Genera aleatorios criptográficamente fuertes, lo que significa que son adecuados para tokens de seguridad, contraseñas, claves de cifrado, números de tarjeta, etc.

1
2
3
4
5
import java.security.SecureRandom;

SecureRandom sr = new SecureRandom();
int token = sr.nextInt(1_000_000, 10_000_000);   // numero de 7 digitos
System.out.println(token);

Por qué no usar Random para seguridad. Random y ThreadLocalRandom son predecibles: si un atacante conoce la semilla (por ejemplo, observando el reloj del sistema), puede reproducir toda la secuencia. SecureRandom usa fuentes del sistema operativo (ruido del hardware, movimientos del ratón, etc.) que son prácticamente imposibles de predecir.

No lo profundizamos aquí porque la criptografía está fuera del alcance de esta unidad, pero es importante que sepas que existe y que nunca uses Random o ThreadLocalRandom para nada relacionado con seguridad.


6. Diagrama de las clases de aleatorios en Java

Use mouse to pan and zoom
classDiagram
    class Random {
        +Random()
        +Random(long semilla)
        +int nextInt()
        +int nextInt(int bound)
        +double nextDouble()
        +boolean nextBoolean()
        +long nextLong()
        +double nextGaussian()
        +IntStream ints(int n, int min, int max)
        +void setSeed(long s)
    }
    class ThreadLocalRandom {
        +static ThreadLocalRandom current()
        +int nextInt(int min, int max)
        +double nextDouble(double min, double max)
        +boolean nextBoolean()
        +long nextLong()
    }
    class SecureRandom {
        +SecureRandom()
        +int nextInt()
        +byte[] generateSeed(int n)
    }
    Random <|-- ThreadLocalRandom
    Random <|-- SecureRandom

    note for ThreadLocalRandom "No se instancia con new.<br/>No admite semilla explicita.<br/>Un generador por hilo."
    note for SecureRandom "Aleatorios criptograficamente fuertes.<br/>Usar SIEMPRE para seguridad."
    note for Random "Para reproducibilidad, usar<br/>new Random(semilla)."

7. Ejemplo integrador

Programa que demuestra los conceptos de la sección: generadores con y sin semilla, métodos principales, y comparación con ThreadLocalRandom.

package dam.programacion.ud2;

import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

/**
 * Ejemplo integrador de la Seccion 5 de UD2.
 * Demuestra:
 * - Creacion de Random con y sin semilla
 * - Reproducibilidad con semilla
 * - Metodos nextInt, nextDouble, nextBoolean, nextLong, nextGaussian
 * - Generacion en rango [min, max]
 * - ThreadLocalRandom como mejor practica moderna
 */
public class GeneradoresAleatorios {

    public static void main(String[] args) {

        // 1. Random sin semilla: cada ejecucion da valores distintos
        Random g1 = new Random();
        System.out.println("Sin semilla (g1):");
        for (int i = 0; i < 5; i++) {
            System.out.print(g1.nextInt(100) + " ");
        }
        System.out.println();

        // 2. Random con semilla: secuencia reproducible
        Random g2a = new Random(42);
        Random g2b = new Random(42);
        System.out.println("Con semilla 42 (g2a):");
        for (int i = 0; i < 5; i++) {
            System.out.print(g2a.nextInt(100) + " ");
        }
        System.out.println();
        System.out.println("Con semilla 42 (g2b):");
        for (int i = 0; i < 5; i++) {
            System.out.print(g2b.nextInt(100) + " ");
        }
        System.out.println();

        // 3. Generacion en rango [min, max]
        System.out.println("Tiradas de dado (1-6):");
        for (int i = 0; i < 10; i++) {
            System.out.print(g1.nextInt(6) + 1 + " ");
        }
        System.out.println();

        // 4. Otros tipos
        System.out.println("double en [0, 1): " + g1.nextDouble());
        System.out.println("boolean: " + g1.nextBoolean());
        System.out.println("long: " + g1.nextLong());
        System.out.println("gaussiana: " + g1.nextGaussian());

        // 5. Generar password aleatorio de 8 caracteres
        String caracteres = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        StringBuilder password = new StringBuilder();
        for (int i = 0; i < 8; i++) {
            int indice = g1.nextInt(caracteres.length());
            password.append(caracteres.charAt(indice));
        }
        System.out.println("Password generado: " + password);

        // 6. ThreadLocalRandom: mejor practica moderna
        System.out.println("ThreadLocalRandom:");
        int dado = ThreadLocalRandom.current().nextInt(1, 7);
        double temperatura = ThreadLocalRandom.current().nextDouble(15.0, 35.0);
        boolean activo = ThreadLocalRandom.current().nextBoolean();
        System.out.println("  dado: " + dado);
        System.out.println("  temperatura: " + temperatura);
        System.out.println("  activo: " + activo);

        // 7. Simulacion: lanzar 1000 dados y contar cuantas veces sale cada cara
        int[] contador = new int[6];
        for (int i = 0; i < 1000; i++) {
            int cara = ThreadLocalRandom.current().nextInt(1, 7);
            contador[cara - 1]++;
        }
        System.out.println("Distribucion de 1000 tiradas:");
        for (int i = 0; i < 6; i++) {
            System.out.println("  Cara " + (i + 1) + ": " + contador[i]);
        }
    }
}

Salida (parcial; los valores sin semilla cambian entre ejecuciones):

Sin semilla (g1):
37 51 92 18 73 
Con semilla 42 (g2a):
29 56 27 73 41 
Con semilla 42 (g2b):
29 56 27 73 41 
Tiradas de dado (1-6):
3 6 1 4 2 5 1 6 3 2 
double en [0, 1): 0.4527...
boolean: true
long: 3829103847...
gaussiana: -0.341...
Password generado: aB3dE7kZ
ThreadLocalRandom:
  dado: 4
  temperatura: 23.81...
  activo: true
Distribucion de 1000 tiradas:
  Cara 1: 168
  Cara 2: 174
  Cara 3: 159
  Cara 4: 169
  Cara 5: 165
  Cara 6: 165

Observa dos cosas importantes:

  1. g2a y g2b producen exactamente la misma secuencia porque comparten semilla. Esa es la reproducibilidad.
  2. La distribución de 1000 tiradas no es exactamente uniforme (no sale 166-167 de cada cara), pero se acerca. Cuantas más tiradas, más se aproxima a la uniformidad teórica.

8. Errores comunes

Error Qué pasa Cómo evitarlo
Crear Random dentro de un bucle Cada iteración crea un generador nuevo, posiblemente con la misma semilla temporal → resultados repetidos Crea el Random fuera del bucle, una sola vez
Pasar bound <= 0 a nextInt(int) IllegalArgumentException Asegúrate de que bound sea positivo
Confundir nextInt(bound) con nextInt(min, max) El segundo requiere Java 17+ y el segundo argumento es exclusivo Recuerda: nextInt(6) da 0..5; nextInt(1, 7) da 1..6
Usar Random para seguridad Aleatorios predecibles, vulnerables a ataques Usa SecureRandom para tokens, contraseñas, claves
Instanciar ThreadLocalRandom con new Compila (es protected) pero no funciona como esperas Usa siempre ThreadLocalRandom.current()
Olvidar el import "cannot find symbol" Añade import java.util.Random; o import java.util.concurrent.ThreadLocalRandom;
Esperar que nextDouble(5.0, 10.0) funcione en Random clásico Solo existe desde Java 17 y solo en sobrecargas con origen Verifica @since en el Javadoc
Pensar que nextGaussian() devuelve valores en [0, 1) Devuelve distribución normal con media 0 y desviación 1 (puede ser negativo o mayor que 1) Recuerda: es la campana de Gauss, no un rango acotado

9. Resumen

Concepto Idea clave
Pseudoaleatorio Calculado por una fórmula determinista a partir de una semilla
Semilla Valor inicial que determina toda la secuencia
Reproducibilidad Misma semilla → misma secuencia
Random() Generador con semilla basada en el reloj (no reproducible)
Random(long semilla) Generador reproducible
nextInt(int bound) Entero en [0, bound) (bound exclusivo)
nextInt(int min, int max) Entero en [min, max) (Java 17+, max exclusivo)
Fórmula clásica de rango g.nextInt(max - min + 1) + min para [min, max]
nextDouble() Decimal en [0.0, 1.0)
nextBoolean() true o false con probabilidad 1/2
nextGaussian() Distribución normal (media 0, desviación 1)
ThreadLocalRandom.current() Mejor práctica moderna; un generador por hilo
SecureRandom Para seguridad: contraseñas, tokens, claves

10. Cuestionario de autoevaluación

Preguntas

  1. ¿Por qué se dice que los números generados por Random son "pseudoaleatorios" y no "verdaderamente aleatorios"?

  2. ¿Qué es la semilla de un generador y por qué es importante?

  3. Si creas dos objetos Random con new Random(42), ¿qué relación hay entre las secuencias que producen?

  4. Escribe una línea que genere un entero entre 1 y 10 (ambos inclusive) usando Random.

  5. Escribe la misma generación del ejercicio 4 usando la sobrecarga de Java 17+.

  6. ¿Qué rango de valores puede devolver nextInt(6) (sin argumentos adicionales)?

  7. ¿Qué devuelve nextBoolean()? ¿Y nextDouble()? ¿Y nextGaussian() puede devolver valores negativos?

  8. ¿Por qué Random tiene problemas de rendimiento cuando se usa desde muchos hilos?

  9. ¿Cómo se obtiene una instancia de ThreadLocalRandom? ¿Por qué no se usa new?

  10. ¿Puedes ponerle una semilla a ThreadLocalRandom para reproducir una secuencia? ¿Por qué?

  11. ¿Qué clase usarías para generar un token de seguridad para restablecer una contraseña? ¿Por qué no Random?

  12. ¿Qué error obtienes si llamas a nextInt(0) o nextInt(-5)?

  13. Escribe las líneas para simular 100 tiradas de una moneda y contar cuántas "caras" (true) salen.

  14. ¿Cuál es la diferencia entre nextInt(int bound) y nextInt(int origin, int bound)?

  15. Si en un test de JUnit necesitas generar datos aleatorios pero quieres que el test sea determinista (siempre dé la misma salida), ¿qué clase y qué constructor usarías?

Soluciones

  1. Porque están calculados por una fórmula matemática determinista a partir de una semilla. La secuencia parece aleatoria (pasa tests estadísticos), pero si conoces la semilla puedes reproducirla exactamente.

  2. La semilla es el valor inicial a partir del cual la fórmula genera toda la secuencia. Es importante porque determina completamente la secuencia: misma semilla, misma secuencia. Esto permite reproducibilidad en tests y depuración.

  3. Producen exactamente la misma secuencia. Cada llamada a nextInt, nextDouble, etc. devuelve los mismos valores en el mismo orden en ambos objetos.

  4. int n = g.nextInt(10) + 1; (donde g es un Random previamente creado).

  5. int n = g.nextInt(1, 11); (recordando que el segundo argumento es exclusivo).

  6. nextInt(6) devuelve un entero en [0, 6), es decir, 0, 1, 2, 3, 4 o 5. Nunca devuelve 6.

  7. nextBoolean() devuelve true o false con probabilidad 1/2 cada uno. nextDouble() devuelve un double en [0.0, 1.0). Sí, nextGaussian() puede devolver valores negativos: sigue una distribución normal con media 0 y desviación 1, así que aproximadamente la mitad de las veces devuelve un valor negativo.

  8. Porque Random usa internamente una variable compartida protegida con sincronización. Si muchos hilos llaman simultáneamente a nextInt sobre el mismo objeto, deben hacer cola, lo que provoca contención y reduce el rendimiento.

  9. Con ThreadLocalRandom.current(), método estático que devuelve una instancia única por hilo. No se usa new porque el constructor es protected (acceso restringido) y porque la API está diseñada para dar a cada hilo su propio generador interno, no para crear generadores arbitrarios.

  10. No. ThreadLocalRandom no permite establecer una semilla explícita. Es una decisión de diseño: como cada hilo tiene su propio generador inicializado internamente, no se garantiza reproducibilidad. Para reproducibilidad usa new Random(semilla).

  11. SecureRandom. Porque Random y ThreadLocalRandom son predecibles: si un atacante conoce o adivina la semilla (por ejemplo, observando el reloj del sistema), puede reproducir toda la secuencia y predecir los tokens. SecureRandom usa fuentes del sistema operativo imposibles de predecir.

  12. IllegalArgumentException, porque bound debe ser estrictamente positivo.

  13. Una solución:

    1
    2
    3
    4
    5
    6
    7
    8
    Random g = new Random();
    int caras = 0;
    for (int i = 0; i < 100; i++) {
        if (g.nextBoolean()) {
            caras++;
        }
    }
    System.out.println("Caras: " + caras);
    

  14. nextInt(int bound) devuelve un entero en [0, bound). nextInt(int origin, int bound) (Java 17+) devuelve un entero en [origin, bound), permitiendo especificar el origen del rango, no solo el tamaño.

  15. new Random(semilla) con una semilla fija (por ejemplo, new Random(42)). Así cada ejecución del test genera los mismos datos y el resultado es determinista.

Evaluación del cuestionario

Aciertos Nivel
14-15 Dominas la sección. Adelante con la siguiente.
10-13 Bien, repasa los puntos fallados.
6-9 Repasa la sección, especialmente la diferencia entre Random y ThreadLocalRandom.
Menos de 6 Vuelve a leer con calma y ejecuta el ejemplo integrador paso a paso en IntelliJ.

11. Información ampliada y próximos pasos

Próximos pasos:

  1. Practica en IntelliJ. Copia el ejemplo integrador y ejecútalo varias veces. Comprueba que las secuencias con semilla son idénticas y las sin semilla cambian. Modifica el ejemplo del password para usar ThreadLocalRandom en vez de Random.
  2. Experimenta con la simulación de dados. Cambia el número de tiradas (100, 1.000, 10.000, 100.000) y observa cómo la distribución se aproxima a la uniformidad teórica (1/6 ≈ 16,67% por cada cara).
  3. Avanza a la Sección 6 de UD2. Fechas y tiempos con java.time: por qué java.time reemplaza a Date y Calendar, las clases LocalDate, LocalTime, LocalDateTime, los intervalos Period y Duration, y el formateo con DateTimeFormatter. Es una de las secciones más útiles del curso porque casi ninguna aplicación real se libra de trabajar con fechas.