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 the mapToObj and forEach 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:

randomNumberOriginx < 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).

Similar Posts