The several flavors of random in Java
Random number generation is one of most basic features in any programming language. The basic utilization is always the same: generate a random number between 0 and 1. With such a simple resource at hand we sometimes overlook some interesting features.
What do we learn from the books?
The most obvious and maybe intuitive way to generate random numbers in Java is simply calling:
Random generation is in the Math utility class with abs, pow, floor, sqrt and other mathematical functions we see in all other languages. Most people will learn about this class in books, tutorials and classes. The utilization is simple: a double is returned between 0.0 and 1.0. With this basic information, a developer usually writes something like:
Math.random() * 10
…to generate a double between 0.0 and 10.0, and:
Math.round(Math.random() * 10)
…to generate a integer between 0 and 10. Static imports can make these expressions shorter but casting can be annoying, especially if wrapper classes are involved.
One step further
By reading the source code of Math.random() or simply playing with the IDE’s auto-completion feature, a developer can easily notice that java.lang.Math.random() uses an internal random generator object - a powerful object with the flexibility to generate randoms of booleans, all numeric types (with and without range) and even Gaussian distributed doubles. For example:
This amazing object does have one drawback, however: it is an object. Its methods are only accessible through an instance, which means the constructor must be called. Expressions like the one above are acceptable if memory is not an issue, but it feels that something is not right.
A simple solution to avoid creating a new instance of Random each time you need a random number is to have a singleton instance using a static class, but by doing this you will be essentially replicating what java.lang.Math does. Maybe a safe way is to copy the lazy initialization and have your improved version of java.lang.Math. This works, has low maintenance, but you may want some simple unit testing just to make sure it is working.
The storage problem comes from the assumption that the code which calls it only wants a random value. Sometimes there is a need to manipulate or protect the seed, an internal number used to store the state and calculate the next random number. In these special cases a public accessible random generator is not appropriate.
In a multi-threaded environment like a Java EE application, the random generator object instance can still be stored as a static attribute in a class or some other implementation of the singleton pattern. Fortunately, java.util.Random is thread-safe so there is no risk to corrupt the seed with calls from multiple threads.
Another option worth considering in a multi-threaded scenario is a java.lang.ThreadLocal instance. The idea of a lazy initialized singleton is implemented by a native Java API and you also have a guarantee of one instance object per thread. No more trouble with concurrency and no more trouble with unit testing.
One thing is still not right: Java did not provide a good way to manage the singleton java.util.Random instance and through this post it was discussed ways to build that. Well, the long awaited Java 7 provides a new way to generate random values:
This new API mixes the advantages of the two other approaches: singleton/static access like Math.random() and flexibility. ThreadLocalRandom is also faster than any other approach in high concurrence scenarios.
Fellow summanoid Chris Marasti-Georg pointed that
Math.round(Math.random() * 10)
causes an unbalanced distribution, e.g.: 0.0 - 0.499999 will round to 0 while 0.5 to 1.499999 will round to 1. The correct way to achieve a balanced distribution with the old style syntax is
Math.floor(Math.random() * 11)
Fortunately we don’t have to worry if we are java.util.Random or java.util.concurrent.ThreadLocalRandom.
Effective Java item 47 describes some the dangers of not using the java.util.Random API in the correct way. The lesson is do not use
instead use the API as designed: rnd.nextInt(n).