mtorchiano / FibonacciStream.java
The generation of a Fibonacci sequence can be carried on in several ways:
- using the generate() method
- leaning on an external class to keep the state and perform the computation (method fibonacciGG() )
- using a lambda with an external class just to keep the state (method fibonacciGLP() )
- using a lambda storing the state into an external array (method fibonacciGLa() )
- using a lambda with an external class to keep the state (method fibonacciILP() )
- using a lambda with an array to keep the state (method fibonacciILa() )
- using a lambda with an array to keep the state that is re-created at each iteration (method fibonacciILan() )
The four implementations are shown in file FibonacciStream.java in the corresponding methods.
The time performance of the five alternatives above are reported in the following table and illustrated in the diagram.
Solution Method Time Throughput generate(Generator class) fibonacciGG() 94.5 106 generate(lambda, array) fibonacciGLP() 98.5 102 generate(lambda, Pair) fibonacciGLa() 93.5 107 iterate(lambda, new array) fibonacciILan() 70.0 143 iterate(lambda, array) fibonacciILP() 35.0 286 iterate(lambda, Pair) fibonacciILa() 31.0 323 The performance are based on CPU times collected on a Mac with 2.3 GHz Intel Core i7.
The time measure is the time required to generate a 10M numbers sequence.
The throughput is expressed in Million numbers generated per second.
The two best iterate() solutions outperfom the generate() based solutions by almost three times.
In both cases we observe that the array variants are performing slightly worsed than the ones using an external class. This is likely due to the additional checks performed when accessing an array.
The new array version of the iterate() deserves some additional consideration. It is the solution typically suggested in several places (several stackoverflow answers) as it is the most compact and possibly elegant. Unfortunately it creates a new object every iteration, this solution is inefficient both from a time and memory occupation perspectives.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
package streams ; import java . util . stream . LongStream ; import java . util . stream . Stream ; public class FibonacciStream public static LongStream fibonacciGG () class FibonacciGenerator int prev = 0 ; int prevPrev = 0 ; public int next () int result = prev + prevPrev ; if ( prevPrev == 0 ) result = 1 ; prevPrev = prev ; return prev = result ; > > FibonacciGenerator g = new FibonacciGenerator (); return LongStream . generate ( g :: next ); > public static LongStream fibonacciGLa () long [] fibo = < 0 , 0 >; return LongStream . generate (()-> long result = fibo [ 0 ]+ fibo [ 1 ]; if ( fibo [ 1 ]== 0 ) result = 1 ; fibo [ 1 ] = fibo [ 0 ]; return fibo [ 0 ] = result ; >); > public static LongStream fibonacciGLP () class Pair long prev ; long prevPrev ; > Pair fib = new Pair (); return LongStream . generate (()-> long result = fib . prev + fib . prevPrev ; if ( fib . prevPrev == 0 ) result = 1 ; fib . prevPrev = fib . prev ; return fib . prev = result ; >); > public static LongStream fibonacciILa () return Stream . iterate ( new long [] < 1 , 0 >, ( f )-> long result = f [ 0 ]+ f [ 1 ]; if ( f [ 1 ]== 0 ) result = 1 ; f [ 1 ] = f [ 0 ]; f [ 0 ] = result ; return f ; >) . mapToLong ( f -> f [ 0 ]); > public static LongStream fibonacciILP () class Pair return Stream . iterate ( new Pair (), ( p )-> long result = p . pre + p . prePre ; if ( p . prePre == 0 ) result = 1 ; p . prePre = p . pre ; p . pre = result ; return p ; >) . mapToLong ( p -> p . pre ); > public static LongStream fibonacciILan () return Stream . iterate ( new long [] < 1 , 0 >, ( f )-> new long [] < f [ 0 ]+ f [ 1 ], f [ 0 ]>) . mapToLong ( f -> f [ 0 ]); > > Java Stream generate()
This page will walk through Stream.generate method example. The generate method returns an infinite sequential unordered stream where each element is generated by the provided Supplier . The generate method is used to generate constant streams and streams of random elements.
Find the generate method signature from Java doc.static Stream generate(Supplier s)
Parameters:
Pass the Supplier using which stream elements are generated.
Returns:
It returns a new infinite sequential unordered Stream .Stream stream = Stream.generate(() -> new Random().nextInt(10)); stream.forEach(e -> System.out.println(e));
Stream.generate(() -> new Random().nextBoolean()) .forEach(e -> System.out.println(e));
Stream.generate(() -> "Hello World!") .forEach(e -> System.out.println(e));
Hello World! Hello World! Hello World! ---
Example-2
As we know that generate returns infinite sequential stream, to limit the number of elements in stream, we can use Stream.limit method.
LimitGenerateDemo.javapackage com.concretepage; import java.util.Random; import java.util.stream.Stream; public class LimitGenerateDemo < public static void main(String[] args) < Stream.generate(() ->new Random().nextInt(10)).limit(3) .forEach(e -> System.out.println(e)); Stream.generate(() -> new Random().nextBoolean()).limit(3) .forEach(e -> System.out.println(e)); Stream.generate(() -> "Hello World!").limit(3) .forEach(e -> System.out.println(e)); > >
3 1 3 true false false Hello World! Hello World! Hello World!
Example-3
Find the generate method declaration from IntStream , LongStream and DoubleStream .
For IntStreamstatic IntStream generate(IntSupplier s)
static LongStream generate(LongSupplier s)
static DoubleStream generate(DoubleSupplier s)
package com.concretepage; import java.util.Random; import java.util.stream.DoubleStream; import java.util.stream.IntStream; import java.util.stream.LongStream; public class GenerateDemo < public static void main(String[] args) < System.out.println("--- IntStream ---"); IntStream.generate(() ->new Random().nextInt()).limit(3) .forEach(e -> System.out.println(e)); System.out.println("--- LongStream ---"); LongStream.generate(() -> new Random().nextLong()).limit(3) .forEach(e -> System.out.println(e)); System.out.println("--- DoubleStream ---"); DoubleStream.generate(() -> new Random().nextDouble()).limit(3) .forEach(e -> System.out.println(e)); > >
--- IntStream --- 1208611141 1228590173 1248692128 --- LongStream --- -1743816652457802629 -5799826804201802284 795354974140733326 --- DoubleStream --- 0.8264963255616102 0.6543249573645461 0.037807208216310784
Interface Stream
A sequence of elements supporting sequential and parallel aggregate operations. The following example illustrates an aggregate operation using Stream and IntStream :
int sum = widgets.stream() .filter(w -> w.getColor() == RED) .mapToInt(w -> w.getWeight()) .sum();
In this example, widgets is a Collection
. We create a stream of Widget objects via Collection.stream() , filter it to produce a stream containing only the red widgets, and then transform it into a stream of int values representing the weight of each red widget. Then this stream is summed to produce a total weight. In addition to Stream , which is a stream of object references, there are primitive specializations for IntStream , LongStream , and DoubleStream , all of which are referred to as «streams» and conform to the characteristics and restrictions described here.
To perform a computation, stream operations are composed into a stream pipeline. A stream pipeline consists of a source (which might be an array, a collection, a generator function, an I/O channel, etc), zero or more intermediate operations (which transform a stream into another stream, such as filter(Predicate) ), and a terminal operation (which produces a result or side-effect, such as count() or forEach(Consumer) ). Streams are lazy; computation on the source data is only performed when the terminal operation is initiated, and source elements are consumed only as needed.
A stream implementation is permitted significant latitude in optimizing the computation of the result. For example, a stream implementation is free to elide operations (or entire stages) from a stream pipeline — and therefore elide invocation of behavioral parameters — if it can prove that it would not affect the result of the computation. This means that side-effects of behavioral parameters may not always be executed and should not be relied upon, unless otherwise specified (such as by the terminal operations forEach and forEachOrdered ). (For a specific example of such an optimization, see the API note documented on the count() operation. For more detail, see the side-effects section of the stream package documentation.)
Collections and streams, while bearing some superficial similarities, have different goals. Collections are primarily concerned with the efficient management of, and access to, their elements. By contrast, streams do not provide a means to directly access or manipulate their elements, and are instead concerned with declaratively describing their source and the computational operations which will be performed in aggregate on that source. However, if the provided stream operations do not offer the desired functionality, the BaseStream.iterator() and BaseStream.spliterator() operations can be used to perform a controlled traversal.
A stream pipeline, like the «widgets» example above, can be viewed as a query on the stream source. Unless the source was explicitly designed for concurrent modification (such as a ConcurrentHashMap ), unpredictable or erroneous behavior may result from modifying the stream source while it is being queried.
- must be non-interfering (they do not modify the stream source); and
- in most cases must be stateless (their result should not depend on any state that might change during execution of the stream pipeline).
Such parameters are always instances of a functional interface such as Function , and are often lambda expressions or method references. Unless otherwise specified these parameters must be non-null.
A stream should be operated on (invoking an intermediate or terminal stream operation) only once. This rules out, for example, «forked» streams, where the same source feeds two or more pipelines, or multiple traversals of the same stream. A stream implementation may throw IllegalStateException if it detects that the stream is being reused. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.
Streams have a BaseStream.close() method and implement AutoCloseable . Operating on a stream after it has been closed will throw IllegalStateException . Most stream instances do not actually need to be closed after use, as they are backed by collections, arrays, or generating functions, which require no special resource management. Generally, only streams whose source is an IO channel, such as those returned by Files.lines(Path) , will require closing. If a stream does require closing, it must be opened as a resource within a try-with-resources statement or similar control structure to ensure that it is closed promptly after its operations have completed.
Stream pipelines may execute either sequentially or in parallel. This execution mode is a property of the stream. Streams are created with an initial choice of sequential or parallel execution. (For example, Collection.stream() creates a sequential stream, and Collection.parallelStream() creates a parallel one.) This choice of execution mode may be modified by the BaseStream.sequential() or BaseStream.parallel() methods, and may be queried with the BaseStream.isParallel() method.