Numeri casuali in Java spiegati: Math.random(), Random, SecureRandom e modelli di intervallo

目次

1. Cosa Imparerai in Questo Articolo

When you try to work with “random numbers” in Java, you’ll quickly run into multiple options such as Math.random(), Random, and SecureRandom.
Many people end up thinking, “Which one am I supposed to use?”

In this section, we’ll start with the bottom line and clarify what you will be able to do by reading this article to the end. By understanding the big picture before diving into detailed mechanisms and code, the later sections will become much easier to follow.

1.1 Capirai i Principali Metodi per Generare Numeri Casuali in Java

This article explains the major ways to generate random numbers in Java step by step.

Specifically, you’ll learn:

  • A simple method you can use immediately → Un metodo semplice che puoi usare subito
  • A method that allows more programmatic control → Un metodo che consente un maggiore controllo programmatico
  • A method for situations where security matters → Un metodo per situazioni in cui la sicurezza è importante

We’ll organize everything by use case so it’s easy to choose the right approach.

That means this article works for both:

  • Beginners who want to try sample code → Principianti che vogliono provare il codice di esempio
  • People who want to move beyond “it runs, so it’s fine” → Persone che vogliono andare oltre “funziona, quindi è ok”

The structure is designed to support both types of readers.

1.2 Capirai le Regole di Intervallo e le Idee Sbagliate Comuni

One of the biggest stumbling blocks when working with random numbers is range selection.

For example:

  • You want a random number from 0 to 9 → Vuoi un numero casuale da 0 a 9
  • You want to simulate a die roll from 1 to 6 → Vuoi simulare il lancio di un dado da 1 a 6
  • You want random numbers that include negative values → Vuoi numeri casuali che includano valori negativi

In these cases, questions like the following come up constantly:

  • Is the upper bound included? → Il limite superiore è incluso?
  • Is the lower bound always included? → Il limite inferiore è sempre incluso?
  • Why am I not getting the values I expected? → Perché non ottengo i valori che mi aspettavo?

This article explains it in a beginner-friendly flow:
“Why does it happen?” → “How do you write it correctly?”

1.3 Imparerai la Differenza tra Riproducibilità e Rischio

Random numbers can have opposite requirements depending on the situation:

  • You want different results every time → Vuoi risultati diversi ogni volta
  • You want to reproduce the same results repeatedly → Vuoi riprodurre gli stessi risultati più volte

For example:

  • For testing and debugging, you want to reproduce “the same random values” → Per test e debug, vuoi riprodurre “gli stessi valori casuali”
  • For passwords and tokens, you need “unpredictable random values” → Per password e token, hai bisogno di “valori casuali imprevedibili”

If you use them without understanding this difference, you can run into issues such as:

  • Unstable tests → Test instabili
  • Security-dangerous implementations → Implementazioni pericolose per la sicurezza

This article clearly separates:
“Randomness that should be reproducible” vs. “Randomness that must not be reproducible”

1.4 Sarai in Grado di Scegliere “Casualità Sicura” vs. “Casualità Pericolosa”

Java provides multiple random-number options that may look similar, but their purposes are completely different.

  • Randomness suitable for games and sample code → Casualità adatta per giochi e codice di esempio
  • Randomness that is fine for business applications → Casualità adeguata per applicazioni aziendali
  • Randomness that is absolutely required for security use cases → Casualità assolutamente necessaria per casi d’uso di sicurezza

If you use them without distinguishing the purpose, you’re likely to end up with:

  • “It seems to work, but it’s actually dangerous” → “Sembra funzionare, ma è in realtà pericoloso”
  • “Code that becomes a problem later” → “Codice che diventa un problema in seguito”

This article explains the decision criteria with reasons, in the form:
“For this use case, use this.”

1.5 Anche i Principianti Saranno in Grado di Spiegare “Perché Questo è il Modo Giusto”

Instead of just listing code examples, we focus on the reasoning behind them, such as:

  • Why this method is used → Perché questo metodo è usato
  • Why this specific style is correct → Perché questo stile specifico è corretto
  • Why other approaches are not suitable → Perché altri approcci non sono adatti

We emphasize the background and way of thinking.

So this is useful for people who want to:

  • Understand and use it (not just memorize) → Capire e usarlo (non solo memorizzare)
  • Reach a level where they can explain it to others → Raggiungere un livello in cui possono spiegarlo ad altri

as well.

2. Cosa Significano i “Numeri Casuali” in Java

Before generating random numbers in Java, this section organizes the core fundamentals you must know.
If you skip this and only copy code, you will almost certainly get confused later.

2.1 “Random” Non Significa “Perfettamente Casuale”

Un’idea sbagliata comune tra i principianti è questa:
I numeri casuali generati in Java non sono “perfettamente casuali”.

La maggior parte dei valori casuali usati in Java è più accuratamente descritta come:

  • Numeri calcolati secondo regole fisse (un algoritmo)
  • Sembrano casuali, ma internamente seguono uno schema

Questo tipo di casualità è chiamato numero pseudocasuale (output PRNG).

2.2 Cos’è un Generatore di Numeri Pseudocasuali (PRNG)?

Un generatore di numeri pseudocasuali è un meccanismo che:

  • Parte da un valore iniziale (un seed)
  • Ripete calcoli matematici
  • Produce una sequenza che sembra casuale

I vantaggi principali includono:

  • Generazione veloce
  • Lo stesso seed riproduce la stessa sequenza casuale
  • Facile da gestire sui computer

D’altra parte, gli svantaggi includono:

  • Prevedibile se l’algoritmo è noto
  • Potrebbe non essere adatto per casi d’uso di sicurezza

2.3 Quando la “Casualità Riproducibile” è Utile

A prima vista, potresti pensare:

Se gli stessi valori casuali appaiono, non è forse non casuale e quindi sbagliato?

Ma nella pratica, la casualità riproducibile è estremamente importante.

Ad esempio:

  • Vuoi verificare gli stessi risultati ogni volta nel codice di test
  • Vuoi riprodurre una segnalazione di bug
  • Vuoi confrontare i risultati di simulazioni

In queste situazioni, è molto più pratico avere:

  • Gli stessi valori casuali ad ogni esecuzione
  • piuttosto che risultati che cambiano ogni volta

Molte classi Java per numeri casuali sono progettate tenendo conto di questa riproducibilità.

2.4 Quando la Casualità NON Deve Essere Riproducibile

D’altra parte, alcuni valori casuali non devono mai essere riproducibili.

Esempi tipici includono:

  • Generazione di password
  • Token di autenticazione
  • ID di sessione
  • Chiavi monouso

Se questi valori sono:

  • Prevedibili
  • Riproducibili

ciò da solo può portare a un grave incidente di sicurezza.

Ecco perché Java fornisce
generatori casuali focalizzati sulla sicurezza
separati dai normali generatori pseudocasuali.

Se implementi senza comprendere questa differenza, potresti facilmente finire con:

  • Codice che “funziona” ma è pericoloso
  • Codice che diventa un problema in revisioni o audit

Quindi assicurati di comprendere questo punto.

2.5 Cos’è una “Distribuzione Uniforme”? (Le Basi del Bias)

Un termine che appare spesso nelle discussioni sui numeri casuali è distribuzione uniforme.

Una distribuzione uniforme significa:

  • Ogni valore appare con la stessa probabilità

Ad esempio:

  • Un dado dove 1–6 sono ugualmente probabili
  • Le cifre da 0 a 9 appaiono uniformemente

Quello stato è una distribuzione uniforme.

Le API casuali di Java sono generalmente progettate assumendo una distribuzione uniforme.

2.6 Esempi Comuni per Principianti di “Casualità Distorta”

Quando regoli manualmente i numeri casuali, puoi introdurre accidentalmente bias.

Esempi comuni includono:

  • Forzare un intervallo usando % (l’operatore modulo)
  • Castare double a int nel punto sbagliato
  • Non comprendere se i limiti sono inclusivi

Questi sono insidiosi perché il codice continua a funzionare, il che rende l’errore
difficile da notare.

Le sezioni successive spiegheranno, con esempi concreti:

  • Perché si verifica il bias
  • Come scriverlo correttamente

così potrai evitare queste insidie.

3. Iniziare Rapidamente con Math.random()

Da qui, vedremo modi concreti per generare numeri casuali in Java.
Il primo metodo è Math.random(), il più semplice e il più comunemente incontrato dai principianti.

3.1 Cos’è Math.random()?

Math.random() è un metodo statico fornito da Java che restituisce un valore double casuale maggiore o uguale a 0.0 e minore di 1.0.

double value = Math.random();

Quando esegui questo codice, il valore restituito è:

  • maggiore o uguale a 0.0
  • minore di 1.0

In altre parole, 1.0 non è mai incluso.

3.2 Perché Math.random() è Così Facile da Usare

Il più grande vantaggio di Math.random() è che
non richiede assolutamente alcuna configurazione.

  • Nessuna istanziazione di classe
  • Nessuna dichiarazione di import
  • Utilizzabile in una singola riga

Per questo, è molto comodo per:

  • Esempi di apprendimento
  • Demo semplici
  • Verificare rapidamente il flusso del programma

in tali situazioni.

3.3 Generare numeri interi casuali con Math.random()

Nei programmi reali, spesso si desiderano numeri interi casuali
piuttosto che valori double.

3.3.1 Generare un numero casuale da 0 a 9

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

I valori prodotti da questo codice sono:

  • 0 o maggiore
  • 9 o minore

Ecco perché:

  • Math.random() restituisce valori da 0.0 a 0.999…
  • Moltiplicando per 10 si ottiene da 0.0 a 9.999…
  • Il cast a int tronca la parte decimale

3.4 Un errore comune nella generazione da 1 a 10

Un errore molto comune per i principianti è dove spostare il valore di partenza.

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

Questo produce numeri casuali che sono:

  • maggiori o uguali a 1
  • minori o uguali a 10

Se sbagli l’ordine e scrivi invece questo:

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

Questo codice produce:

  • (int)Math.random() è sempre 0
  • Il risultato diventa sempre 1

Avvolgi sempre il cast tra parentesi — questo è un punto critico.

3.5 Vantaggi e limitazioni di Math.random()

Vantaggi

  • Estremamente semplice
  • Basso costo di apprendimento
  • Sufficiente per casi d’uso piccoli e semplici

Limitazioni

  • Nessuna riproducibilità (nessun controllo del seed)
  • Nessun controllo interno
  • Non adatto per casi d’uso di sicurezza
  • Manca di flessibilità per scenari complessi

In particolare, se hai bisogno di:

  • Gli stessi valori casuali nei test
  • Controllo fine sul comportamento

allora Math.random() non sarà sufficiente.

3.6 Quando dovresti usare Math.random()

Math.random() è più adatto per:

  • Apprendimento Java nelle fasi iniziali
  • Codice di spiegazione degli algoritmi
  • Esempi di verifica semplici

D’altra parte, per:

  • Applicazioni aziendali
  • Codice di test
  • Logica legata alla sicurezza

dovresti scegliere un generatore di numeri casuali più appropriato.

4. Comprendere la classe principale java.util.Random

Ora passiamo a un passo oltre Math.random() e guardiamo
la classe java.util.Random.

Random è una classe fondamentale usata da molti anni in Java. Appare quando si desidera
“un controllo adeguato sui numeri casuali”.

4.1 Cos’è la classe Random?

Random è una classe per generare numeri pseudocasuali.
La si utilizza creando un’istanza così:

import java.util.Random;

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

La più grande differenza rispetto a Math.random() è che
il generatore di numeri casuali è trattato come un oggetto.

Questo ti permette di:

  • Riutilizzare lo stesso generatore
  • Mantenere il comportamento coerente
  • Gestire esplicitamente la casualità nel tuo design

cosa non è possibile con Math.random().

4.2 Tipi di valori casuali che puoi generare con Random

La classe Random fornisce metodi adattati a diversi casi d’uso.

Esempi comuni includono:

  • nextInt() : un valore int casuale
  • nextInt(bound) : un int da 0 (inclusivo) a bound (esclusivo)
  • nextLong() : un valore long casuale
  • nextDouble() : un double da 0.0 (inclusivo) a 1.0 (esclusivo)
  • nextBoolean() : true o false

Scegliendo il metodo giusto, puoi generare valori casuali
naturalmente adatti a ciascun tipo di dato.

5. Controllare intervallo e riproducibilità con Random

Uno dei maggiori vantaggi dell’uso di java.util.Random è
il controllo esplicito su intervalli e riproducibilità.

5.1 Generare valori entro un intervallo specifico

Il metodo più comunemente usato è nextInt(bound).

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

Questo codice produce valori che sono:

  • maggiori o uguali a 0
  • minori di 10

Quindi l’intervallo di risultato è da 0 a 9.

5.2 Spostare l’intervallo (ad es., da 1 a 10)

Per spostare l’intervallo, basta aggiungere un offset:

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

Questo produce valori da:

  • 1 (inclusivo)
  • 10 (inclusivo)

Questo schema:

random.nextInt(range) + start

è il modo standard per generare valori interi casuali all’interno di un intervallo in Java.

5.3 Generare Numeri Casuali con Intervalli Negativi

È anche possibile generare intervalli che includono valori negativi.

Ad esempio, per generare valori da -5 a 5:

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

Spiegazione:

  • nextInt(11) → da 0 a 10
  • Sottrai 5 → da -5 a 5

Questo approccio funziona in modo coerente indipendentemente dal segno dell’intervallo.

5.4 Riproducibilità con i Seed

Un’altra caratteristica chiave di Random è il controllo del seed.

Se specifichi un seed, la sequenza casuale diventa riproducibile:

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

Finché viene usato lo stesso valore di seed:

  • Viene generata la stessa sequenza di valori casuali

Questo è estremamente utile per:

  • Test unitari
  • Confronti di simulazioni
  • Debug di bug difficili

Al contrario, Math.random() non consente il controllo esplicito del seed.

6. Perché Random non è adatto per la sicurezza

A questo punto, potresti pensare:

Random può generare valori imprevedibili, quindi perché non è adatto per la sicurezza?

Questo è un fraintendimento molto comune.

6.1 La prevedibilità è il problema principale

java.util.Random utilizza un algoritmo deterministico.

Ciò significa:

  • Se l’algoritmo è noto
  • Se vengono osservati sufficienti valori di output

allora i valori futuri possono essere previsti.

Per giochi o simulazioni, questo non è un problema.
Per la sicurezza, è un difetto critico.

6.2 Esempi concreti di utilizzo pericoloso

Usare Random per quanto segue è pericoloso:

  • Generazione di password
  • Chiavi API
  • Identificatori di sessione
  • Token di autenticazione

Anche se i valori “sembrano casuali”, non sono crittograficamente sicuri.

6.3 La differenza chiave tra “aspetto casuale” e “sicuro”

La distinzione critica è la seguente:

  • Random : veloce, riproducibile, prevedibile in teoria
  • Secure random : imprevedibile, resistente all’analisi

La casualità orientata alla sicurezza deve essere progettata assumendo che:

  • L’attaccante conosca l’algoritmo
  • L’attaccante possa osservare gli output

Random non soddisfa questo requisito.

Ecco perché Java fornisce una classe separata specificamente per i casi d’uso di sicurezza.

7. Utilizzare SecureRandom per la casualità critica per la sicurezza

Quando la casualità deve essere imprevedibile e resistente agli attacchi,
Java fornisce java.security.SecureRandom.

Questa classe è progettata specificamente per casi d’uso sensibili alla sicurezza.

7.1 Cosa rende SecureRandom diverso?

SecureRandom differisce da Random nei suoi obiettivi di progettazione.

  • Utilizza algoritmi crittograficamente robusti
  • Preleva entropia da più fonti di sistema
  • Progettato per essere imprevedibile anche se gli output sono osservati

A differenza di Random, lo stato interno di SecureRandom non è praticamente reversibile.

7.2 Uso base di SecureRandom

L’uso è simile a Random, ma l’intento è molto diverso.

import java.security.SecureRandom;

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

Questo produce valori da:

  • 0 (inclusivo)
  • 10 (esclusivo)

L’API è intenzionalmente simile in modo da poter sostituire Random quando necessario.

7.3 Quando è necessario usare SecureRandom

Dovresti usare SecureRandom per:

  • Generazione di password
  • ID di sessione
  • Token di autenticazione
  • Chiavi crittografiche

In questi scenari, usare Random non è “leggermente rischioso” — è scorretto.

Il costo prestazionale di SecureRandom è intenzionale e accettabile per la sicurezza.

8. API Random moderne: ThreadLocalRandom e RandomGenerator

Le versioni recenti di Java forniscono API random più avanzate per affrontare problemi di prestazioni e di progettazione.

8.1 ThreadLocalRandom: Casualità per il Multithreading

ThreadLocalRandom è ottimizzato per ambienti multithread.

Invece di condividere un’unica istanza di Random, ogni thread utilizza il proprio generatore.

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

Questo genera valori da 1 (inclusivo) a 11 (esclusivo).

I vantaggi includono:

  • Nessuna contesa tra i thread
  • Migliore prestazioni in presenza di concorrenza
  • API basate su intervalli puliti

Per l’elaborazione parallela, questo è solitamente preferibile a Random.

8.2 RandomGenerator: Un’interfaccia unificata (Java 17+)

RandomGenerator è un’interfaccia introdotta per unificare la generazione di numeri casuali.

Consente:

  • Cambiare facilmente gli algoritmi
  • Scrivere codice indipendente dall’implementazione
  • Progettazione più a prova di futuro
    import java.util.random.RandomGenerator;
    
    RandomGenerator generator = RandomGenerator.getDefault();
    int value = generator.nextInt(10);
    

Questo approccio è consigliato per codebase Java moderne.

9. Riepilogo: Scegliere l’API Random Giusta in Java

Java fornisce diverse API per i numeri casuali perché
“casualità” ha significati diversi a seconda del contesto.

9.1 Guida Rapida alla Decisione

  • Math.random() : apprendimento, demo semplici
  • Random : test, simulazioni, comportamento riproducibile
  • ThreadLocalRandom : applicazioni multithread
  • RandomGenerator : design moderno e flessibile
  • SecureRandom : password, token, sicurezza

Scegliere quella sbagliata potrebbe non causare errori immediati,
ma può creare problemi seri in seguito.

9.2 Il Principio Chiave da Ricordare

Il punto più importante è questo:

La casualità è una decisione di progettazione, non solo una chiamata di funzione.

Capendo l’intento dietro ogni API, puoi scrivere codice Java che è:

  • Corretto
  • Manutenibile
  • Sicuro

E adatto per applicazioni reali.