Java-Zufallszahlen erklärt: Math.random(), Random, SecureRandom und Bereichsmuster

目次

1. Was Sie in diesem Artikel lernen werden

Wenn Sie versuchen, mit „Zufallszahlen“ in Java zu arbeiten, stoßen Sie schnell auf mehrere Optionen wie Math.random(), Random und SecureRandom.
Viele Menschen denken schließlich: „Welche soll ich benutzen?“

In diesem Abschnitt beginnen wir mit dem Fazit und verdeutlichen, was Sie bis zum Ende des Artikels erreichen können. Wenn Sie das große Ganze verstehen, bevor Sie in detaillierte Mechanismen und Code eintauchen, werden die späteren Abschnitte viel leichter zu folgen sein.

1.1 Sie verstehen die wichtigsten Methoden zur Erzeugung von Zufallszahlen in Java

Dieser Artikel erklärt die wichtigsten Methoden zur Erzeugung von Zufallszahlen in Java Schritt für Schritt.

Konkret lernen Sie:

  • Eine einfache Methode, die Sie sofort einsetzen können
  • Eine Methode, die mehr programmatische Kontrolle ermöglicht
  • Eine Methode für Situationen, in denen Sicherheit wichtig ist

Wir ordnen alles nach Anwendungsfall, damit die richtige Vorgehensweise leicht zu wählen ist.

Das bedeutet, der Artikel ist für beide Zielgruppen geeignet:

  • Anfänger, die Beispielcode ausprobieren wollen
  • Personen, die über „es läuft, also ist es okay“ hinausgehen wollen

Die Struktur ist darauf ausgelegt, beide Lesertypen zu unterstützen.

1.2 Sie verstehen Bereichsregeln und gängige Missverständnisse

Eines der größten Stolpersteine bei Zufallszahlen ist die Auswahl des Wertebereichs.

Zum Beispiel:

  • Sie wollen eine Zufallszahl von 0 bis 9
  • Sie wollen einen Würfelwurf von 1 bis 6 simulieren
  • Sie wollen Zufallszahlen, die auch negative Werte enthalten

In diesen Fällen tauchen ständig Fragen wie die folgenden auf:

  • Ist die obere Grenze inklusive?
  • Ist die untere Grenze immer inklusive?
  • Warum erhalte ich nicht die erwarteten Werte?

Dieser Artikel erklärt das in einem anfängerfreundlichen Ablauf:
„Warum passiert das?“ → „Wie schreibt man es korrekt?“

1.3 Sie lernen den Unterschied zwischen Reproduzierbarkeit und Risiko

Zufallszahlen können je nach Situation entgegengesetzte Anforderungen haben:

  • Sie wollen jedes Mal andere Ergebnisse
  • Sie wollen dieselben Ergebnisse wiederholt reproduzieren können

Zum Beispiel:

  • Beim Testen und Debuggen möchten Sie „die gleichen Zufallswerte“ reproduzieren
  • Für Passwörter und Tokens benötigen Sie „unvorhersehbare Zufallswerte“

Wenn Sie sie verwenden, ohne diesen Unterschied zu verstehen, können Probleme wie folgende auftreten:

  • Instabile Tests
  • Sicherheitsgefährdende Implementierungen

Dieser Artikel trennt klar:
„Zufälligkeit, die reproduzierbar sein sollte“ vs. „Zufälligkeit, die nicht reproduzierbar sein darf“

1.4 Sie können „sichere“ vs. „gefährliche“ Zufälligkeit wählen

Java bietet mehrere Optionen für Zufallszahlen, die ähnlich aussehen, deren Zwecke jedoch völlig unterschiedlich sind.

  • Zufälligkeit, die für Spiele und Beispielcode geeignet ist
  • Zufälligkeit, die für Business‑Anwendungen in Ordnung ist
  • Zufälligkeit, die für sicherheitsrelevante Anwendungsfälle zwingend erforderlich ist

Wenn Sie sie ohne Unterscheidung des Zwecks verwenden, endet das häufig in:

  • „Es scheint zu funktionieren, ist aber tatsächlich gefährlich“
  • „Code, der später zum Problem wird“

Dieser Artikel erklärt die Entscheidungskriterien mit Begründungen in der Form:
„Für diesen Anwendungsfall verwenden Sie das.“

1.5 Auch Anfänger können erklären, „warum das der richtige Weg ist“

Statt nur Codebeispiele aufzulisten, konzentrieren wir uns auf die dahinterstehende Logik, zum Beispiel:

  • Warum diese Methode verwendet wird
  • Warum dieser spezielle Stil korrekt ist
  • Warum andere Ansätze nicht geeignet sind

Wir betonen den Hintergrund und die Denkweise.

Damit ist der Artikel nützlich für Personen, die:

  • Es verstehen und anwenden wollen (nicht nur auswendig lernen)
  • Ein Niveau erreichen wollen, auf dem sie es anderen erklären können

ebenso.

2. Was „Zufallszahlen“ in Java bedeuten

Bevor Sie in Java Zufallszahlen erzeugen, ordnet dieser Abschnitt die wesentlichen Grundlagen, die Sie kennen müssen.
Wenn Sie das überspringen und nur Code kopieren, werden Sie fast sicher später verwirrt sein.

2.1 „Random“ bedeutet nicht „perfekt zufällig“

Ein häufiger Anfänger‑Fehlvorstellung ist diese:
Zufallszahlen, die in Java erzeugt werden, sind nicht „perfekt zufällig“.

Die meisten in Java verwendeten Zufallswerte werden genauer beschrieben als:

  • Zahlen, die nach festen Regeln (einem Algorithmus) berechnet werden
  • Sie sehen zufällig aus, folgen aber intern einem Muster

Diese Art von Zufälligkeit wird Pseudorandom‑Zahl (PRNG‑Ausgabe) genannt.

2.2 Was ist ein Pseudorandom‑Number‑Generator (PRNG)?

Ein Pseudorandom‑Number‑Generator ist ein Mechanismus, der:

  • Von einem Anfangswert (einem Seed) ausgeht
  • Mathematische Berechnungen wiederholt
  • Eine Sequenz erzeugt, die zufällig erscheint

Wesentliche Vorteile umfassen:

  • Schnelle Erzeugung
  • Der gleiche Seed reproduziert die gleiche Zufallssequenz
  • Einfach auf Computern zu handhaben

Auf der anderen Seite gibt es Nachteile:

  • Vorhersehbar, wenn der Algorithmus bekannt ist
  • Kann für Sicherheitsanwendungen ungeeignet sein

2.3 Wann „reproduzierbare Zufälligkeit“ nützlich ist

Auf den ersten Blick könnte man denken:

Wenn dieselben Zufallswerte erscheinen, ist das nicht zufällig und damit schlecht?

In der Praxis ist reproduzierbare Zufälligkeit extrem wichtig.

Zum Beispiel:

  • Sie möchten in Testcode jedes Mal die gleichen Ergebnisse verifizieren
  • Sie möchten einen Bug‑Report reproduzieren
  • Sie möchten Simulationsergebnisse vergleichen

In diesen Situationen ist es weitaus praktischer, zu haben:

  • Die gleichen Zufallswerte bei jedem Durchlauf
  • statt dass die Ergebnisse jedes Mal variieren

Viele Java‑Zufallszahlklassen sind mit dieser Reproduzierbarkeit im Hinterkopf entworfen.

2.4 Wann Zufälligkeit NICHT reproduzierbar sein darf

Auf der anderen Seite dürfen einige Zufallswerte niemals reproduzierbar sein.

Typische Beispiele sind:

  • Passwortgenerierung
  • Authentifizierungstoken
  • Session‑IDs
  • Einmalige Schlüssel

Wenn diese Werte:

  • Vorhersehbar
  • Reproduzierbar

kann das allein zu einem schweren Sicherheitsvorfall führen.

Deshalb stellt Java
sicherheitsfokussierte Zufallsgeneratoren
bereit, die von gewöhnlichen Pseudorandom‑Generatoren getrennt sind.

Wenn Sie implementieren, ohne diesen Unterschied zu verstehen, können Sie leicht enden mit:

  • Code, der „funktioniert“, aber gefährlich ist
  • Code, der in Reviews oder Audits zum Problem wird

Stellen Sie also sicher, dass Sie diesen Punkt verstehen.

2.5 Was ist eine „gleichmäßige Verteilung“? (Die Grundlagen von Bias)

Ein Begriff, der häufig in Diskussionen über Zufallszahlen auftaucht, ist gleichmäßige Verteilung.

Eine gleichmäßige Verteilung bedeutet:

  • Jeder Wert erscheint mit derselben Wahrscheinlichkeit

Zum Beispiel:

  • Ein Würfel, bei dem 1–6 gleich wahrscheinlich sind
  • Ziffern 0–9 erscheinen gleichmäßig

Dieser Zustand ist eine gleichmäßige Verteilung.

Javas Zufalls‑APIs sind im Allgemeinen so konzipiert, dass sie eine gleichmäßige Verteilung annehmen.

2.6 Häufige Anfängerbeispiele für „verzerrte Zufälligkeit“

Wenn Sie manuell „Zufallszahlen anpassen“, können Sie versehentlich Bias einführen.

Häufige Beispiele sind:

  • Einen Bereich mit % (dem Restoperator) erzwingen
  • double zu int zur falschen Zeit casten
  • Missverständnis, ob die Grenzen inklusiv sind

Diese sind knifflig, weil der Code trotzdem läuft, was den Fehler schwer zu bemerken macht.

Spätere Abschnitte werden mit konkreten Beispielen erklären:

  • Warum der Bias entsteht
  • Wie man es korrekt schreibt

damit Sie diese Fallstricke vermeiden können.

3. Schnellstart mit Math.random()

Ab hier betrachten wir konkrete Wege, um Zufallszahlen in Java zu erzeugen.
Die erste Methode ist Math.random(), die einfachste und von Anfängern am häufigsten begegnete.

3.1 Was ist Math.random()?

Math.random() ist eine statische Methode von Java, die einen double‑Zufallswert zurückgibt, der größer oder gleich 0.0 und kleiner als 1.0.

double value = Math.random();

Wenn Sie diesen Code ausführen, ist der zurückgegebene Wert:

  • größer oder gleich 0.0
  • kleiner als 1.0

Mit anderen Worten, 1.0 ist nie enthalten.

3.2 Warum Math.random() so einfach zu benutzen ist

Der größte Vorteil von Math.random() ist, dass
es absolut keine Einrichtung erfordert.

  • Keine Klasseninstanziierung
  • Keine Import-Anweisungen
  • In einer einzigen Zeile verwendbar

Deshalb ist es sehr praktisch für:

  • Lernbeispiele
  • Einfache Demos
  • Schnelles Überprüfen des Programmflusses

in solchen Situationen.

3.3 Ganzzahlige Zufallszahlen mit Math.random() erzeugen

In realen Programmen möchten Sie oft ganzzahlige Zufallszahlen
anstelle von double-Werten.

3.3.1 Eine Zufallszahl von 0 bis 9 erzeugen

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

Die von diesem Code erzeugten Werte sind:

  • 0 oder größer
  • 9 oder kleiner

Hier ist der Grund:

  • Math.random() gibt Werte von 0.0 bis 0.999… zurück
  • Multiplikation mit 10 ergibt 0.0 bis 9.999…
  • Cast zu int kürzt den Dezimalteil ab

3.4 Ein häufiger Fehler beim Erzeugen von 1 bis 10

Ein sehr häufiger Anfängerfehler ist wo der Startwert verschoben wird.

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

Dies erzeugt Zufallszahlen, die sind:

  • größer oder gleich 1
  • kleiner oder gleich 10

Wenn Sie die Reihenfolge falsch machen und stattdessen dies schreiben:

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

Dieser Code führt zu:

  • (int)Math.random() ist immer 0
  • Das Ergebnis wird immer 1

Umfassen Sie den Cast immer mit Klammern—das ist ein kritischer Punkt.

3.5 Vorteile und Einschränkungen von Math.random()

Vorteile

  • Extrem einfach
  • Geringer Lernaufwand
  • Ausreichend für kleine, einfache Anwendungsfälle

Einschränkungen

  • Keine Reproduzierbarkeit (keine Seed-Kontrolle)
  • Keine interne Kontrolle
  • Nicht geeignet für Sicherheitsanwendungsfälle
  • Fehlende Flexibilität für komplexe Szenarien

Insbesondere, wenn Sie benötigen:

  • Dieselben Zufallswerte in Tests
  • Feingranulare Kontrolle über das Verhalten

dann reicht Math.random() nicht aus.

3.6 Wann Sie Math.random() verwenden sollten

Math.random() eignet sich am besten für:

  • Frühe Phasen des Java-Lernens
  • Code zur Erklärung von Algorithmen
  • Einfache Verifizierungsbeispiele

Andererseits, für:

  • Geschäftsanwendungen
  • Testcode
  • Sicherheitsbezogene Logik

sollten Sie einen geeigneteren Zufallszahlengenerator wählen.

4. Das Verständnis der Kernklasse java.util.Random

Nun gehen wir einen Schritt über Math.random() hinaus und schauen uns
die java.util.Random-Klasse an.

Random ist eine fundamentale Klasse, die seit vielen Jahren in Java verwendet wird. Sie kommt zum Einsatz, wenn Sie
„ordentliche Kontrolle über Zufallszahlen“ wollen.

4.1 Was ist die Random-Klasse?

Random ist eine Klasse zur Erzeugung pseudozufälliger Zahlen.
Sie verwenden sie, indem Sie eine Instanz wie diese erstellen:

import java.util.Random;

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

Der größte Unterschied zu Math.random() ist, dass
der Zufallszahlengenerator als Objekt behandelt wird.

Das ermöglicht Ihnen:

  • Wiederverwendung desselben Generators
  • Konsistentes Verhalten zu halten
  • Explizite Verwaltung der Zufälligkeit in Ihrem Design

was mit Math.random() nicht möglich ist.

4.2 Arten von Zufallswerten, die Sie mit Random erzeugen können

Die Random-Klasse stellt Methoden zur Verfügung, die auf verschiedene Anwendungsfälle zugeschnitten sind.

Häufige Beispiele umfassen:

  • nextInt() : ein int-Zufallswert
  • nextInt(bound) : ein int von 0 (inklusiv) bis bound (exklusiv)
  • nextLong() : ein long-Zufallswert
  • nextDouble() : ein double von 0.0 (inklusiv) bis 1.0 (exklusiv)
  • nextBoolean() : true oder false

Indem Sie die richtige Methode wählen, können Sie Zufallswerte erzeugen,
die natürlich zu jedem Datentyp passen.

5. Bereich und Reproduzierbarkeit mit Random steuern

Einer der größten Vorteile der Verwendung von java.util.Random ist
explizite Kontrolle über Bereiche und Reproduzierbarkeit.

5.1 Werte innerhalb eines spezifischen Bereichs erzeugen

Die am häufigsten verwendete Methode ist nextInt(bound).

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

Dieser Code erzeugt Werte, die sind:

  • größer oder gleich 0
  • kleiner als 10

Daher ist der Ergebnisbereich 0 bis 9.

5.2 Den Bereich verschieben (z. B. 1 bis 10)

Um den Bereich zu verschieben, fügen Sie einfach einen Offset hinzu:

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

Dies erzeugt Werte von:

  • 1 (einschließlich)
  • 10 (einschließlich)

Dieses Muster:

random.nextInt(range) + start

ist der Standardweg, um ganzzahlige Zufallswerte innerhalb eines Bereichs in Java zu erzeugen.

5.3 Generieren von Zufallszahlen mit negativen Bereichen

Sie können auch Bereiche erzeugen, die negative Werte enthalten.

Zum Beispiel, um Werte von -5 bis 5 zu erzeugen:

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

Erklärung:

  • nextInt(11) → 0 bis 10
  • Subtrahiere 5 → -5 bis 5

Dieser Ansatz funktioniert konsistent unabhängig vom Vorzeichen des Bereichs.

5.4 Reproduzierbarkeit mit Seeds

Eine weitere wichtige Funktion von Random ist die Seed-Steuerung.

Wenn Sie einen Seed angeben, wird die Zufallssequenz reproduzierbar:

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

Solange derselbe Seed-Wert verwendet wird:

  • Wird dieselbe Sequenz von Zufallswerten erzeugt

Dies ist äußerst nützlich für:

  • Unit-Tests
  • Simulationsvergleiche
  • Debugging schwieriger Bugs

Im Gegensatz dazu erlaubt Math.random() keine explizite Seed-Steuerung.

6. Warum Random nicht für Sicherheit geeignet ist

Zu diesem Zeitpunkt könnten Sie denken:

Random kann unvorhersehbare Werte erzeugen, also ist es doch in Ordnung für Sicherheit?

Dies ist ein sehr gängiges Missverständnis.

6.1 Vorhersagbarkeit ist das Kernproblem

java.util.Random verwendet einen deterministischen Algorithmus.

Das bedeutet:

  • Wenn der Algorithmus bekannt ist
  • Wenn genügend Ausgabewerte beobachtet werden

können zukünftige Werte vorhergesagt werden.

Für Spiele oder Simulationen ist das kein Problem.
Für Sicherheit ist das ein kritischer Mangel.

6.2 Konkrete Beispiele für gefährliche Nutzung

Die Verwendung von Random für Folgendes ist gefährlich:

  • Passwort-Generierung
  • API-Schlüssel
  • Session-Identifikatoren
  • Authentifizierungstokens

Auch wenn die Werte „zufällig aussehen“, sind sie nicht kryptografisch sicher.

6.3 Der entscheidende Unterschied zwischen „zufällig aussehend“ und „sicher“

Der kritische Unterschied ist dieser:

  • Random : schnell, reproduzierbar, theoretisch vorhersagbar
  • Secure random : unvorhersehbar, widerstandsfähig gegen Analysen

Sicherheitsbezogene Zufälligkeit muss unter der Annahme entworfen werden, dass:

  • Der Angreifer den Algorithmus kennt
  • Der Angreifer Ausgaben beobachten kann

Random erfüllt diese Anforderung nicht.

Deshalb stellt Java eine separate Klasse speziell für Sicherheitsfälle zur Verfügung.

7. Verwendung von SecureRandom für sicherheitskritische Zufälligkeit

Wenn Zufälligkeit unvorhersehbar und widerstandsfähig gegen Angriffe sein muss,
stellt Java java.security.SecureRandom zur Verfügung.

Diese Klasse ist speziell für sicherheitsempfindliche Anwendungsfälle konzipiert.

7.1 Was macht SecureRandom anders?

SecureRandom unterscheidet sich von Random in seinen Designzielen.

  • Verwendet kryptografisch starke Algorithmen
  • Zieht Entropie aus mehreren Systemquellen
  • Entwickelt, um unvorhersehbar zu sein, auch wenn Ausgaben beobachtet werden

Im Gegensatz zu Random ist der interne Zustand von SecureRandom nicht praktisch umkehrbar.

7.2 Grundlegende Nutzung von SecureRandom

Die Nutzung ist ähnlich wie bei Random, aber die Absicht ist sehr unterschiedlich.

import java.security.SecureRandom;

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

Dies erzeugt Werte von:

  • 0 (einschließlich)
  • 10 (ausschließlich)

Die API ist absichtlich ähnlich, damit sie Random bei Bedarf ersetzen kann.

7.3 Wann Sie SecureRandom verwenden müssen

Sie sollten SecureRandom für Folgendes verwenden:

  • Passwort-Generierung
  • Session-IDs
  • Authentifizierungstokens
  • Kryptografische Schlüssel

In diesen Szenarien ist die Verwendung von Random nicht „leicht riskant“ – sie ist falsch.

Die Leistungskosten von SecureRandom sind intentional und für Sicherheit akzeptabel.

8. Moderne Random-APIs: ThreadLocalRandom und RandomGenerator

Neuere Java-Versionen bieten fortschrittlichere Random-APIs, um Leistungs- und Designprobleme zu adressieren.

.### 8.1 ThreadLocalRandom: Zufälligkeit für Multithreading

ThreadLocalRandom ist für multithreaded Umgebungen optimiert.

Anstatt eine einzige Random‑Instanz zu teilen, verwendet jeder Thread seinen eigenen Generator.

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

Dies erzeugt Werte von 1 (inklusive) bis 11 (exklusiv).

Vorteile umfassen:

  • Keine Kontention zwischen Threads
  • Bessere Performance bei Parallelität
  • Saubere, bereichsbasierte APIs

Für parallele Verarbeitung ist dies in der Regel Random vorzuziehen.

8.2 RandomGenerator: Eine einheitliche Schnittstelle (Java 17+)

RandomGenerator ist ein Interface, das eingeführt wurde, um die Erzeugung von Zufallszahlen zu vereinheitlichen.

Es ermöglicht:

  • Einfaches Wechseln von Algorithmen
  • Schreiben von implementierungsunabhängigem Code
  • Zukunftssicheres Design
    import java.util.random.RandomGenerator;
    
    RandomGenerator generator = RandomGenerator.getDefault();
    int value = generator.nextInt(10);
    

Dieser Ansatz wird für moderne Java‑Codebasen empfohlen.

9. Zusammenfassung: Die richtige Random‑API in Java wählen

Java bietet mehrere Random‑Number‑APIs, weil
„Zufälligkeit“ je nach Kontext unterschiedliche Bedeutungen hat.

9.1 Schnell‑Entscheidungs‑Leitfaden

  • Math.random() : Lernen, einfache Demos
  • Random : Tests, Simulationen, reproduzierbares Verhalten
  • ThreadLocalRandom : Multithread‑Anwendungen
  • RandomGenerator : Modern, flexibles Design
  • SecureRandom : Passwörter, Tokens, Sicherheit

Die falsche Wahl verursacht möglicherweise nicht sofort Fehler,
kann aber später ernsthafte Probleme hervorrufen.

9.2 Die zentrale Grundregel

Die wichtigste Erkenntnis lautet:

Zufälligkeit ist eine Design‑Entscheidung, nicht nur ein Funktionsaufruf.

Wenn Sie die Absicht hinter jeder API verstehen, können Sie Java‑Code schreiben, der:

  • Korrekt
  • Wartbar
  • Sicher

und für den Einsatz in realen Anwendungen geeignet ist.