Generating random values with the Stream API
The Stream API is one of the great features introduced in the JDK 8 release. You often use the Stream API to operate on values from standard collections, files, databases, or other data structures. However, sometimes you need to create a new stream from scratch. Furthermore, in some situations, you may need to create a stream of random values. In this article, I will explain this latter case with the help of some concrete examples.
Note: some knowledge of the Stream API, even at a basic level, is a prerequisite for reading this article. If you know what the
java.util.stream.Stream
class is, and what themapToObj
andforEach
operations are, you shouldn’t have any problems.
Firstly, we must distinguish between two slightly different cases: generating a stream of random numbers and a stream of random objects (of any type). These two cases are not totally unrelated, as you will see shortly, but it’s good to treat them separately.
Stream of random numbers
When the Stream API was added in JDK 8, the java.util.Random
class and its subclasses (SecureRandom
and ThreadLocalRandom
) were also updated. In particular, these classes have got 12 new methods, which are as follows:
public DoubleStream doubles()
public DoubleStream doubles(double randomNumberOrigin, double randomNumberBound)
public DoubleStream doubles(long streamSize)
public DoubleStream doubles(long streamSize, double randomNumberOrigin, double randomNumberBound)
public IntStream ints()
public IntStream ints(int randomNumberOrigin, int randomNumberBound)
public IntStream ints(long streamSize)
public IntStream ints(long streamSize, int randomNumberOrigin, int randomNumberBound)
public LongStream longs()
public LongStream longs(long randomNumberOrigin, long randomNumberBound)
public LongStream longs(long streamSize)
public LongStream longs(long streamSize, long randomNumberOrigin, long randomNumberBound)
These are all public “instance” methods; thus, you must first get an instance of Random
(or a subclass of Random
) before using them. Each method creates a new stream of random numbers configured according to the argument values. You can create a random stream of only three primitive types, int
, long
and double
. These are generally sufficient in most cases.
At this point, you should carefully understand the meaning of the streamSize
, randomNumberOrigin
, and randomNumberBound
parameters.
The streamSize parameter
The streamSize
parameter specifies the exact number of values to generate. In other words, the stream has a well-known, finite size. However, if you use one of the variants without the streamSize
parameter, the resulting stream is virtually “infinite”. In this latter case, you must limit the stream size in some other way, for example, using the limit(maxSize)
operation provided by the Stream API.
The randomNumberOrigin and randomNumberBound parameters
These two parameters specify the range of values for generating the random numbers. Pay attention here because randomNumberOrigin
is inclusive while randomNumberBound
is exclusive (this value is never generated). If you are more mathematically inclined, we can say that for any value x randomly generated, the following is always satisfied:
randomNumberOrigin
≤ x < randomNumberBound
If you use one of the variants without the randomNumberOrigin
/randomNumberBound
parameters, the range of values is the broadest possible based on the chosen data type (e.g., from -2147483648 to +2147483647 for a stream of int
values).
A concrete example
At this point, you should notice that generating a stream of random numbers is a straightforward task! The following is a concrete example: we want to create a stream of 30 int
values between -100 and 100 (both extremes included).
import java.util.Random;
import java.util.stream.IntStream;
public class RandomIntStreamDemo {
public static void main(String[] args) {
Random rnd = new Random();
IntStream numStream = rnd.ints(30, -100, 101); // 101 is NOT included!
numStream.forEach(n -> System.out.print(n + " "));
}
}
This is an example of the output produced:
81 83 -40 -21 -20 -2 -49 14 -73 3 55 7 -63 96 -14 97 85 -3 92 -12 -52 -47 95 -88 100 -19 -51 -93 -63 27
Stream of random objects
What if we want to generate a stream of random objects? Before we continue, it’s necessary to clarify the context. Let’s imagine we have the following array:
String[] colorNames = { "red", "green", "blue", "yellow" };
Given this array, we would like to create a Stream<String>
that generates, say, 30 strings randomly taken from the array, e.g., blue
, red
, yellow
, red
, green
, green
, yellow
, red
, blue
, green
, etc.
Unfortunately, the Java SE (Standard Edition) framework does not provide this feature directly. I have tried looking for this functionality in some well-known “utility” libraries like the Apache Commons Lang and Google Guava but found nothing useful.
However, don’t worry! This type of generation is straightforward to implement. We have an array, so we know the length and last index perfectly. We can generate a stream of random indexes with values ranging from 0 inclusive to array.length
exclusive (sounds familiar?) and then “map” each index to the object at that index. Simple, isn’t it?
In the code above, I have used an array of String
objects (a String[]
), but I could have used any other array type. For example, I could have used an array of java.awt.Color
, java.math.BigInteger
, java.time.Year
, an enum
type, and so on. This is an excellent clue to take advantage of another great feature of Java: generics and generic methods.
The following example is one possible solution.
import java.util.Random;
import java.util.stream.Stream;
public class RandomObjectStreamDemo {
public static void main(String[] args) {
String[] colorNames = { "red", "green", "blue", "yellow" };
Random rnd = new Random();
Stream<String> stream = randomObjects(rnd, 30, colorNames);
stream.forEach(s -> System.out.print(s + " "));
}
public static <T> Stream<T> randomObjects(Random rnd, long streamSize, T[] objects) {
return rnd.ints(streamSize, 0, objects.length).mapToObj(i -> objects[i]);
}
}
This is an example of the output produced:
red blue blue blue green green yellow red yellow yellow blue green yellow blue yellow green green red red yellow green red red red blue green red blue yellow yellow
If you notice, randomObjects
is a “generic” method (in the sense of Java generics) because it declares the type variable T
. Thanks to generics, if you pass a String[]
to randomObjects
, you get a Stream<String>
; if you pass a Color[]
you get a Stream<Color>
and so on.
The most relevant part of the code is the mapToObj(i -> objects[i])
. It “maps” (=transforms) the index i
to the object at that index. And that’s really all!
Conclusions
In this article, we have seen how to create a stream of random numbers, which is very easy, and a stream of random objects, which is just as simple.
If you have understood the general concept, you can apply as many variations as you like. For example, you can generate a stream of random objects from a list (I mean java.util.List
, and preferably the ArrayList
implementation). The concept is exactly the same. You use list.size()
instead of objects.length
and then list.get(i)
instead of objects[i]
.
Furthermore, if you want to create something more reusable, you can also think of encapsulating this functionality in an appropriate class that may be used, for example, in the following way:
StreamRandomizer streamRnd = new StreamRandomizer();
Stream<String> stream = streamRnd.randomObjects(30, colorNames);
Creating such StreamRandomizer
class is left to the reader as an “exercise” (if you have doubts, don’t hesitate to contact me).