Java 17 - RandomGenerator i spółka
Dziś szybki rzut oka 👁️ na biblioteki przydatne podczas generowania losowych danych (przydają się podczas pisania testów oraz do prototypowania) i głębsze zanurzenie 🌊 w pakiet java.util.random
.
Przyglądam się klasiejava.util.Random
oraz pakietowi java.util.random
w kontekście nowych interfejsów, jakie pojawiły się w Javie 17 (patrz Java 17 - co nowego).
Nowe interfejsy to m.in. RandomGenerator, RandomGenerator.StreamableGenerator, RandomGenerator.JumpableGenerator, RandomGenerator.SplittableGenerator.
Generowanie danych dla testów
Jeśli Twoje programy kiedykolwiek potrzebowały testów, to testy zapewne potrzebowały testowych danych. Masz szczęście, jeśli dane do testów to dane rzeczywiste (na przykład historyczne dane transakcji, operacji bankowych, zdarzeń w systemie).
Gorzej, jeśli musisz sobie radzić inaczej i chcesz wygenerować dane losowe.
W takiej sytuacji możesz się posiłkować gotowymi bibliotekami do generowania “mock data”:
Github | Stars | Page |
---|---|---|
Java Faker | 2.8K ⭐ | http://dius.github.io/java-faker |
Easy Random | 1.2K ⭐ | https://github.com/j-easy/easy-random/wiki |
Datagen | 55 ⭐ | http://qala.io/blog/randomized-testing.html |
Czasem jednak możesz po prostu nabrać ochoty do eksperymentowania z własnymi pomysłami. Możesz więc zakasać rękawy i rzucić okiem na klasyczny java.util.Random lub przyjrzeć się nowym sposobom uzyskiwania generatorów w Javie 17: użyciu RandomGeneratorFactory
czy wyborze generatora za pomocą nazwy algorytmu (np.: RandomGenerator g = RandomGenerator.of("L64X128MixRandom")
).
Trochę historii
Przed 1.7: Random i SecureRandom
Przed Javą 1.7 istniały tylko dwie klasy: Random i klasa pochodna SecureRandom:
Java 1.7: ThreadLocalRandom
W Javie 1.7 wprowadzona została nowa klasa pochodna od Random: ThreadLocalRandom, której instancje są lokalne dla bieżącego wątku. Choć klasa Random jest threadsafe, jej użycie groziło zmniejszeniem wydajności i wysokim contention wątków przy dostępie do instancji klast Random; klasa ThreadLocalRandom rozwiązuje ten problem.
Java 8
Java 8 i wprowadzenie do języka strumieni pozwoliło na rozszerzenie API klas Random
i SecureRandom
o metody strumieniujące losowe wartości z wariantami pozwalającymi na określenie ilości elementów w strumieniu bądź zakresu wartości (DoubleStream doubles(...)
, IntStream.ints(...)
, LongStream.longs(...)
).
Java 8 wprowadziła również klasę SplittableRandom (niezależną w hierarchii dziedziczenia od klasy Random) z metodą split()
zwracającą nową instancję klasy SplittableRandom
posiadającą odrębny stan. Instancje te nie są threadsafe, a ich główne zastosowanie to użycie niezależnych obiektów generującyh w obliczeniach typu fork/join, np.
|
|
Niestety, klasy Random
i SplittableRandom
nie są “spokrewnione” ani nie posiadają wspólnego interfejsu, więc podmiana jednej klasy na drugą niesie ze sobą całkiem duże ryzyko refaktoryzacyjne.
Java 17 - nowa hierarchia i nowe API
Interfejs Random Generator
Java 17 wprowadziła interfejs RandomGenerator, którego API definiuje wspólny protokół do generowania wartości.
Sekwencję wartości można uzyskać - podobnie jak w przypadku klas Random
czy SecureRandom
- na dwa sposoby: wielokrotnie wywołując poszczególne metody bądź wywołując jedną metodę zwracającą strumień (Stream) wartości. Wartości otrzymane drugim sposobem są wybierane w podobny sposób, lecz nie będą to te same liczby (r.ints(100)
może zwrócić strumień innych wartości, niż strukrotne wywołanie r.nextInt()
).
Mogą być generowane:
- pojedyncze wartości typu
int
,long
,float
,double
lubboolean
wynierane są pseudolosowo z rozkładu jednostajnego - pojedyncze wartości typu
double
uzyskiwane z rozkładu normalnego bądź wykładniczego - strumienie wartości typu
int
,long
idouble
z rozkładu jednostajnego
To główny i najważnieszy interfejs w pakiecie java.util.random. W podlinkowanej dokumentacji można poczytać o szczegółach API, typach generatorów i rodzajach wykorzytsywanych algorytmów. Są tam również sugestie dotyczące tego, jaki algorytm będzie odpowiedni do jakiego rodzaju aplikacji.
Ten interfejs został “wstecznie nałożony” na istniejące klasy, dzięki czemu programiści uzyskują klasyczną możliwość wymiany jednej implementacji generatora na inną, zależnie od bieżących potrzeb.
Interfejs RandomGenerator.StreamableGenerator
Ten interfejs definiuje metody do tworzenia strumieni obiektów typu RandomGenerator
w taki sposób, aby możliwe było równoczesne ich użycie w wielu wątkach. Idea jest tutaj taka, żeby móc łatwo tworzyć statystycznie niezależne generatory (ta właściwość zachodzi z “bardzo dużym prawdopodobieństwem”) i przekazać je do różnych wątków, a nie współdzielić jeden generator, gdyż większość implementacji RandomGenerator
po prostu nie jest thread safe.
Oto nowa hierarchia klas i interfejsów:
Stany i cykle
Javadoc interfejsu RandomGenerator
opisuje kontrakt, a może raczej założenie, jakie spełnia każdy obiekt ten interfejs implementujący:
- każdy obiekt zawiera (albo raczej: jest zdefiniowany przez) pewien skończony stan
- wygenerowanie pseudolosowej wartości jest równoznaczne ze zmianą stanu - przejściem do “nowego” stanu
- wygenerowana wartość zależy wyłącznie od bieżącego stanu
- liczba różnych stanów między którymi “wędruje” obiekt to okres
- niektóre implementacje
RandomGenerator
a mogą być prawdziwie losowe, a nie pseudolosowe; wówczas stan zależy od statystycznego zachowania źródła “losowości” (fizycznego obiektu: termometru, procesora, innych urządzeń wejściowych) i ich okres nie musi być stały
Klasa SplittableRandom implementuje RandomGenerator.StreamableGenerator
Klasa SplittableRandom
implementuje interfejs RandomGenerator.StreamableGenerator
z trzema metodami:
static RandomGenerator.StreamableGenerator of(String name)
zwraca instancję StreamableGeneratora używającego podanego algorytmuStream<RandomGenerator> rngs()
generuje nieskończony strumień obiektów implementujących RandomGeneratordefault Stream<RandomGenerator> rngs(long streamSize)
zwraca skończony strumieństreamSize
obiektów implementującychRandomGenerator
. Główną klasą “wspomagającą” jestRandomGeneratorFactory
.
RandomGeneratorFactory
Java 17 wprowadziła też nową klasę RandomGeneratorFactory, której API pozwala na tworzenie generatorów o właściwościach odpowiednich do rodzaju tworzonej aplikacji. API pozwala też na wybranie fabryki generatorów o specyficznych właściwościach.
Przykład:
Oto kod, który tworzy fabrykę dostarczającą RandomGenerator
y. Następnie tworzy i uruchamia dziesięć wątków. Każdy wątek uzyskuje z fabryki “swój” generator i używa go, losując liczbę do wypisania. Ponieważ za każdym razem do metody create
podawane jest to samo ziarno (seed), powstające generatory będą generowały te same wartości. W poniższym przykładzie zostanie 10 razy wypisana ta sama wartość:
|
|
Oto kod, któru pozwala na wyszukanie fabryki, która produkuje RandomGenerator
y o najwyższej liczbie bitów stanu:
|
|
Podsumowanie
Generowanie losowych wartości nie należy może do codziennych zadań programisty, ale kiedy już trzeba to zrobić, to warto mieć choćby pobieżną orientację w temacie:
RandomGenerator
jest bazowym, wspólnym dla klas generujących interfejsem wprowadzonym w Javie 17- implementuje ją klasa
Random
, która jest threadsafe, lecz użycie jej przez wiele wątków może prowadzić do zmniejszającej wydajność rywalizacji (contention) - do rozwiązań kryptograficznych należy używać klasy
SecureRandom
- w aplikacjach wielowatkowych można używać bezpiecznych dla wątków (threadsafe) instancji
ThreadLocalRandom
(rozwiązuje problem contention) - jeśli wątki nie współdzielą generatora, to mogą również użyć instancji klasy
SplittableRandom
i metody split() SplittableRandom
może być również źródłem strumienia obiektówRandomGenerator
dzięki metodomrngs
- instancje generatorów o pewnych wymaganych właściwościach można również otrzymać korzystając z fabryki generatorów
RandomGeneratorFactory
Mam nadzieję, że artykuł zachęcił Cię do zajrzenia do dokumentacji i poczytania o istniejących oraz tych dodanych w Javie 17 mechanizmach generowania wartości.
Miłedo kodowania!
PS
Zdjęcie kostek do gry: @mickhaupt
Ten wpis jest częścią serii java.
- 2021-09-12 - Java 18: co nowego? - przegląd JEP-ów
- 2021-21-09 - Java 17 - RandomGenerator i spółka
- 2021-15-09 - Java 17 - co nowego?
- 2021-04-03 - Java 15 - czym są sealed classes?
- 2021-26-02 - Java 13 i 14: Bloki tekstowe i rekordy
- 2021-24-02 - Java 12 - wyrażenie switch (preview feature)
- 2021-23-02 - Java 11 - HTTP Client i uruchamianie jednoplikowych programów
- 2021-18-02 - Java 9 - co to jest JShell i dlaczego warto używać REPL-a w Javie
- 2021-12-02 - Java 10 - var, nowe metody w Optional, kolekcje "unmodifiable"
- 2021-11-02 - Java 9 - nowości w bibliotece
- 2021-10-02 - Java 9: czy mogę stworzyć z mojej aplikacji binarkę?
- 2021-10-02 - Java 9: praktyczny przykład - trzy moduły
- 2021-08-02 - Java 8: praktyczny przykład - przewidywanie kolejnej daty w serii