Zufallszahlen sind überall. Und wir brauchen sie auch nicht nur beim Programmieren. Zufallszahlen benötigen wir bei Spielen wie Mensch-Ärger-Dich-Nicht oder beim Pokern. Bei einem Spiel würfeln wir zufällige Zahlen, beim anderen erhalten wir zufällige Karten. Zufallszahlen sind aber nicht nur für Spiele wichtig. Mit Zufallszahlen können wir zum Beispiel starke Passwörter erstellen. Für Zufallszahlen in Java muss man zwischen verschieden Möglichkeiten zur Generierung unterscheiden werden. Im java.util Paket werden drei Klassen für das Generieren von Zufällen zur Verfügung gestellt.
Nachdem du einen groben Einblick in die drei Klassen bekommen hast, beabsichtigen wir uns diese etwas mehr im Detail anzuschauen und wie du diese nun auch verwenden kannst. Am Ende des Artikels findest du, noch ein Beispielprogramm, bei diesem geht es darum, einen kleinen Passwortgenerator zu erstellen.
Wenn du inzwischen anfängst zu recherchieren, wirst du als Erstes auf die Math.random() Methode stoßen. Ich habe einmal die Definition von Math.random() aus der Math Klasse herausgesucht wie dort Random implementiert wurde. Wie du sofort erkennen kannst, basiert diese auf der Random Klasse und gibt immer ein „zufälligen“ Double zurück!
private static final class RandomNumberGeneratorHolder {
static final Random randomNumberGenerator = new Random();
}
public static double random() {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
Die Math.random() Methode gibt, wie oben schon gesagt, ein Double zurück. Der erzeugte Zufallswert ist immer ein Double zwischen 0 und 1. Möchtest du aber eine Zufallszahl zwischen 0 und 10, geht das mit Math.random nicht direkt. Dazu musst du eine zusätzliche Berechnung durchführen. Die Random-Klasse bietet dir dafür nützliche Funktionalität an. Aber dazu später mehr.
double random = Math.random(); // Output z.B: 0.06131309865713075, 0.5334510746461067, 0.41023695343401223
Um nun von der Math.random() Methode einen random Integer zwischen zwei Zahlen, einem Minimum und einem Maximum zu erhalten, kannst du die folgende Berechnung verwenden. Die Berechnung verpacken wir in einer Funktion namens randomInRange
. Der Ansatz ist, dass wir unseren zufälligen Wert mit der Differenz des Minimums und Maximums multiplizieren, um einen Wert von 0 - differenz
zu erhalten. Um dann die Basis beginnend bei min zu haben, addieren wir diese nur noch darauf. Um nur Ganzzahlen zu erhalten, casten wir den Double in einem Integer.
public static int randomInRange(int min, int max) {
return (int) (Math.random() * (max - min)) + min;
}
Wie wir schon im vorherigen Abschnitt gesehen haben, ist Math.random()
nur eine Implementierung / Abrufen von new Random()
und nextDouble()
. Jetzt betrachten wir diese Klasse mal im Detail und was man alles damit machen kann!
import java.util.Random;
Random rn = new Random();
Wenn wir in die Dokumentation von der Random Klasse schauen, finden wir dort acht verschiedene Funktionen nach dem Schema next<Typ>()
– die Methoden für die Typen Float, Double, Int und Long sind vom Schema alle gleich aufgebaut. So erkläre ich diese nun alle an einem Beispiel, bis auf die Typen sind die Funktionsweisen identisch.
Es gibt drei verschiedene Möglichkeiten, die Methoden abzurufen, einmal ohne jegliche Parameter, dann ist es zufällig ohne eine Bereichsbegrenzung. Nur bei Float und Double sind es immer Werte zwischen 0 und 1. Und die Stellen hinter dem Komma entsprechen der Größe jeweils von Float bzw. Double.
// ohne Parameter
float randFloat = rn.nextFloat(); // Output z.B 0.2401365
double randDouble = rn.nextDouble(); // Output z.B 0.11030977416716736
int randInt = rn.nextInt(); // Output z.B 2090563205
long randLong = rn.nextLong(); // Output z.B 101834895284161002
Neben der Variante mit keinem Parameter haben wir auch noch die Variante mit einem Parameter. Dieser Parameter gibt die Größe an, die nicht überschritten werden soll. In unserem Beispiel unten wird die Zufallszahl nie größer als 10. Dem entsprechende, kann hier natürlich nicht zwischen Int und Long unterschieden werden. Diese ist erst wirklich interessant, wenn man die Größe von einem Integer überschreiten will. Heißt der Bound Parameter ist immer exklusive bei den next Methoden der Random Klasse.
// bound Parameter
float randFloat = rn.nextFloat(10); // Output z.B 7.7275615
double randDouble = rn.nextDouble(10); // Output z.B 9.11438802744116
int randInt = rn.nextInt(10); // Output z.B 4
long randLong = rn.nextLong(10); // Output z.B 3
Zu guter Letzt, wenn zwei Parameter angegeben werden, ist der erste Parameter der Startpunkt, also in unserem Fall 150 inklusive (origin
) und bis 200 exklusive (bound
). Entsprechende Beispiele für die verschiedenen Varianten habe ich dir hier noch einmal aufgelistet.
// origin & bound Parameter
float randFloat = rn.nextFloat(150,200); // Output z.B 183.84523
double randDouble = rn.nextDouble(150,200); // Output z.B 190.11630993041155
int randInt = rn.nextInt(150,200); // Output z.B 150
long randLong = rn.nextLong(150,200); // Output z.B 190
Neben den zufälligen Zahlen gibt es zum Beispiel auch die Methode, um einen Boolean zu erhalten, wie das nachfolgende Programm zeigt. Interessant ist allerdings, dass die Implementierung dessen sehr einfach ist.
boolean randBool = rn.nextBoolean();
System.out.println("Willst du mit mir ausgehen?");
if( randBool ) {
System.out.println("Ja gerne!");
} else {
System.out.println("Nein, danke!");
}
Und zwar ist die Implementierung dahinter nur eine Anweisung, ob nextInt()
größer oder kleiner 0 ist. Dort hinter steht also kein Hexenwert, nach einem ähnlichen Vorgehen könntest du in Verwendung eines Arrays auch eine nextWord()
Methode ausdenken. Letztlich basieren alle randomisierten Werte, die keine Zahlen sind, immer noch auf mathematischer Berechnung. Mehr zum Thema Pseudozufall gibt es im nächsten Abschnitt.
default boolean nextBoolean() {
return nextInt() < 0;
}
Ich hatte nun schon in einigen Bereichen auf den sogenannten Pseudozufall hingewiesen. Alles, was wir bisher gezeigt haben, ist kein Zufall, auch wenn wir umgangssprachlich zudem bereits von Zufall gesprochen haben. Der Hintergrund ist, dass ein PC keinen Zufall erstellen kann, solang keine Hardwarekomponenten elektrisches Rauschen oder spezielle Hardware für Zufälle genutzt wird. Den letztlich heißen die Methoden ja nextInt
, nextLong
und nicht randomInt
. Der Teufel steckt im Detail, solange eben nicht externe Einflüsse zugrunde liegen, wird auf eine mathematische Funktion zurückgegriffen, auch bekannt als lineare Kongruenzgenerator oder LCG. Dieser liegt auch bei Java und der Random Klasse zugrunde. Bedeutet, die nächste Zahl ist keine zufällige Zahl, sondern basiert auf der vorherigen. Dadurch sind die Zahlen leicht vorherzusagen, da diese einfach berechnet werden können. Standardmäßig arbeite der Konstruktor von der Random Klasse mit der Methode System.nanoTime()
, wodurch eine Veränderung herbeigeführt wird, dass immer andere Zahlen entstehen, aber sobald die ersten Zahlen der Serie bekannt sind, können die nächsten wieder berechnet werden.
Wenn wir die Random Klasse zum Beispiel mit einem statischen Seed aufrufen, ohne andere Parameter dort einzubeziehen wie eben eine Systemzeit, wird immer dieselbe Reihe an Zahlen entstehen. Schau dir einmal das nächste Codebeispiel an, dort habe ich es einmal verdeutlicht. Falls du mir noch nicht glauben willst, führe dieses Beispiel so oft aus, wie du willst, du wirst immer auf dieselben Zahlen stoßen.
Random rn = new Random(5);
for (int i = 0; i < 10; i++) {
System.out.println(rn.nextInt(150, 200));
}
import java.security.SecureRandom;
Wie wir bei der Beantwortung der Frage, warum der Zufall nicht zufällig ist, gemerkt haben, kann der Zufall vorhergesagt werden. Um dem entgegenzuwirken, gibt es auch eine SecureRandom Klasse, diese erbt von Random. Zu Anfang der Hinweis: in der Ausführung benötigt SecureRandom länger. Also falls du keinen kryptografischen sichereren Zufall benötigst, zum Beispiel bei einem kleinen Spiel, dann würde ich dir Empfehlen bei der Random Klasse zu bleiben.
Die SecureRandom Klasse greift für den Seed auf zufällige Daten zu, die dem Betriebssystem zur Verfügung stehen. Unter Linux zum Beispiel auf /dev/random
. Des Weiteren haben wir auch gesehen, dass wenn wir dieselbe Folge immer wieder ausführen, erhalten wir dieselben Zahlen. Um diese zu verhindern, werden dazwischen weitere Hardware/Betriebssystem bezogene Werte eingespeist. Dadurch erhalten wir eine höhere Sicherheit, aber auch längere Ausführungszeiten.
SecureRandom srn = new SecureRandom();
System.out.println(srn.nextInt());
Nun noch ein kleines Beispiel zur Verwendung, wenn du ein Passwort für deinen Nutzer bei der Registrierung erstellen willst, solltest du SecureRandom verwenden. Beachte, bis auf die Initialisierung unterscheidet sich die Verwendung der Klasse zum Generieren von Zufallszahlen nicht von der Random Klasse. Der Grund dafür ist, dass die SecureRandom Klasse die Random Klasse erweitert. Zusätzlich kannst du noch zwischen verschiedenen Algorithmen wählen, die SecureRandom verwenden soll. Die verschiedenen Algorithmen zur Nummergenerierung kannst du der Dokumentation unter https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#securerandom-number-generation-algorithms entnehmen.
SecureRandom srn = SecureRandom.getInstance("SHA1PRNG");
System.out.println(srn.nextInt());
Die Random Klasse ist standardmäßig so ausgelegt, dass diese auf eine Multithreading Situation optimiert ist. Wie wir oben bereit festgestellt haben, basiert in einem LCG die vorherige Zahl auf der Letzten, jetzt dürfen natürlich nicht mehrere Threads den gleichen Seed nutzen. Weshalb das Schreiben für die anderen Threads in einem kurzen Moment immer blockiert wird, damit diese immer nacheinander generiert werden. Stand jetzt kann es die Situation geben, dass mit Multithreading gearbeitet wird, aber diese Verhalten eigentlich nicht gewünscht ist und es sollen die Zahlen parallel generiert werden können in den verschiedenen Threads. Dafür gibt es die Klasse ThreadLocalRandom, diese ist auf Single-Threaded optimiert. Dementsprechende schneller kann diese arbeiten. Ansonsten erbt sie wieder von Random, weshalb das Arbeiten sich ähnlich verhält. Ein Unterschied ist das Abrufen. Diese geschieht nur über die current
Methode, den der Konstruktor ist privat.
ThreadLocalRandom rn = ThreadLocalRandom.current();
System.out.println(rn.nextInt(150,200));
Nun, da wir alles über Zufallszahlen gelernt haben, können wir mal einen kleinen Passwort-Generator als Beispielanwendung schreiben. Versuch dich gerne einmal selbst daran, bevor du dir den nachfolgenden Code genauer anschaust!
Als Erstes erstellen, wir uns ein String, der alle möglichen Zeichen für unsere Passwörter enthält – wenn du schon Erfahrung mit dem Char hast, kannst du dir die Zeichen auch entsprechende aus einem Integer direkt generieren. Dann benötigen wir die Anzahl an Zeichen, die wir inzwischen definiert haben, diese speichern wir uns in options
. Zusätzlich benötigen wir noch eine Variable, um zu definieren, wie lang unser Passwort sein soll. Anschließende erstellen wir uns noch ein Objekt der Klasse SecureRandom für die Generierung und eins der Klasse StringBuilder, um entsprechende den String einfacher erstellen zu können. Anschließende brauchen wir nur noch eine Schleife, in der wir uns jeweils einen spezifischen Char zufällig aus dem String holen und diesem unserem Passwort hinzufügen. Et voilà, der Passwort-Generator ist fertig!
String lettersNumbers = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
int options = lettersNumbers.length();
int PasswordLen = 20;
SecureRandom secureRandom = new SecureRandom();
StringBuilder password = new StringBuilder(PasswordLen);
for (int i = 0; i < PasswordLen; i++){
int position = secureRandom.nextInt(0, options);
char nextChar = lettersNumbers.charAt(position);
password.append(nextChar);
}
System.out.println(password.toString());
Du kannst jetzt Zahlen mit einem Pseudozufallsgenerator erzeugen, oder mit der Klasse SecureRandom. Falls du das Ganze noch etwas beschleunigen möchtest, kannst du ThreadLocalRandom verwenden.
Hinterlasse mir gerne einen Kommentar zum Artikel und wie er dir weitergeholfen hat beziehungsweise, was dir helfen würde das Thema besser zu verstehen. Oder hast du einen Fehler entdeckt, den ich korrigieren sollte? Schreibe mir auch dazu gerne ein Feedback!
Es sind noch keine Kommentare vorhanden? Sei der/die Erste und verfasse einen Kommentar zum Artikel "Zufällige Zahlen in Java generieren mit der Random Klasse"!