Stream API is another powerful feature added in Java 8 which changes the whole programming style. The interfaces and classes related to Stream API are present in the package "java.util.stream" which are used for processing sequences of objects. Before moving ahead it is highly recommended that you should have basic knowledge of Lambda expression, Functional Interfaces, and Method Reference. Please go through these above topic links before moving ahead.
What are Streams in Java 8?
Streams are sequence or series which are obtained from a different type of sources for example Collections, Arrays, group of values or from an input/output source. Stream API is used for processing the collection of objects or groups of values efficiently and allows us to execute multiple operations on it. Streams and collections differ from each other in many ways.
- Collections are based on some data structures that store elements while streams are not a data structure. Streams carry elements from a source through a pipeline of some computational operations.
- Any operation performed on the collection can modify it while any operation performed on the stream produces a result, but does not modify the source from which the stream obtained.
- Streams operations are classified into two types of operations (i) intermediate operations(produces a new stream) (ii) terminal operations(produces a final result). Intermediate operations are always lazy in streams. Intermediate operations would not be invoked until the terminal operations are invoked. This type of concept is not available in collections.
- Streams are not finite in size while collections are finite. Elements in the collection are visible any number of times till they removed from the collection but in case of stream, an element can be visible only once, and to visit the element again new streams must be created again on the same source.
How to create Streams?
Stream instances can be created in so many ways from different sources. You can create multiple stream instances for a single source because once the instance created it will not modify the source.
Stream instances cab be created
- from any type of collection using stream() and parallelStream() methods present inside the Collection interface in java 8.
- from an array of any type using method Arrays.stream(T[] array) which is an overloaded static method present inside Arrays class in java 8. For example Arrays.stream(int[] array), Arrays.stream(double[] array) etc.
- using the static factory methods of Stream interface (i) Stream.of(T... values), (ii) Stream.builder() (iii) Stream.empty() (iv) Stream.of(T t) (v) Stream.iterate(final T seed, final UnaryOperator<T> f) (vi) Stream.generate(Supplier<T> s) (vii) Stream.concat(Stream<? extends T> a, Stream<? extends T> b) (viii) range() and rangeClosed() of IntStream and LongStream.
- from String objects using a default method chars() which is present inside the interface CharSequence.
- from path of files using methods present inside class Files. For example lines(Path path) and many more.
- using BufferedReader.lines(), it produces a stream of lines of a file.
Below is the example of the above methods of stream creation.
What are stream operations and pipelines?
Stream operations are classified into two types (i) intermediate (ii) terminal operations. A stream pipeline is a combination of a stream source, an intermediate operation, and terminal operation.
Intermediate operations always produce a new stream, for example, filter() operation. It always produces a new stream that contains the objects of the previous stream that matches the condition passed to the filter() method. The filter() method accepts a predicate(a pre-defined functional interface). Intermediate operations are lazy in nature because the execution of intermediate operations doest not invoked until the terminal operation is executed.
Intermediate operations further classified into two categories (i) stateless and (ii) stateful operations. Stateless operations, for example, filter() and map() process a new object without retaining the state of a previously processed object. Every object is processed separately from the operations of other objects. Now if any information of a previously processed object is needed for processing a new object then these operations are called stateful operations. For example, sorting a stream, the result of a sorting operation on a stream cannot be produced until all the objects are processed by that operation and in this case state of every processed object is needed for processing the next object. Intermediate operations are
- Stateful operations: sorted(), distinct(), limit(), skip()
- Stateless operations: filter(), map(), unordered(), peek()
Terminal operations are also known as the end of the stream on which they are traversed. Terminal operations may or may not produce results. When the execution of the terminal operation is completed then the stream pipeline is said to be consumed. You cannot traverse the same stream again if the terminal operation is executed already. For traversing the same data source again, you need to create the stream again. Terminal operations are eager in nature.
Terminal operations are collect(), reduce(), forEach(), toArray(), min(), max(), count(), anymatch(), allMatch(), noneMatch(), findFirst(), findAny()
Now, let's take an example to understand both intermediate and terminal operations.
Now I am ending up this post and will update it or create a new post for some features of the streams like parallel processing, Ordering, short-circuiting, etc. Also, you can share your queries and feedback in the comment section below. Thank you so much.
Comments
Post a Comment