Overview

Java 8 is known for introducing numerous dynamic features. One such notable feature was the Java 8 Streams. The Java 8 streams have been a very unique feature as it offers countless functionalities for Java applications.

In this article we will be discussing what is Java 8 streams, how it works, the operators used with stream and why Java developers must know about them with the help of some practical examples.

explore new Java roles

Java 8 streams

Introduced in Java 8, Streams act as an abstract layer used to process collections of objects. It holds a series of objects that are processed using various operations that are applicable in various algorithms in Java. Java 8 streams allow Java developers to process the collection of objects declaratively by leveraging multicore architecture without any need of writing extra code for it.

It is very important to understand that a stream is not a data structure, it does not store data instead it takes input from other sources such as Collections, I/O channels or Arrays. Java 8 Streams also never manipulate or alter the original data structure, they only return the result after applying the operations.

Stream keywords

Following are some keywords associated with Java 8 Streams:

Elements: The data items present in a stream are called elements. These elements consist of a specific type and are stored sequentially. A stream processes the elements using the operators but it never stores them.

Source: it is the source of data to be processed. Collections, Arrays, and I/O resources are the most common input sources.

Operations: Java 8 Streams supports various aggregate operations such as filter, map, toArray, limit, reduce, find, match, etc. All these operations will be discussed later in this article.

Creating Java 8 Streams

There are several ways to create an instance of a stream from different sources. It also allows the creation of multiple instances from a single source.

Following are some of the ways to create streams for different types of data:

1. Empty Streams

For starters, we can create an empty Java 8 stream with no elements initially using the empty() method. It is primarily used to avoid returning null for streams with no elements.

See this code example below of creating an empty stream:

Stream<String> streamEmpty = Stream.empty();

2. Using Stream.builder()

A stream can be created using the builder. It just requires you to specify the data type you wish to make the stream for in the statement, or else the build() method will automatically create a stream of Objects.

See this example below where a stream of strings is created using the build() method:

Stream<String> streamBuilder =

Stream.<String>builder().add("1").add("2").add("3").add("4").add("5").build();

3. Stream of Collections

A Stream containing any type of Collections such as collection, List or Set is created like this,

Collection<String> collection = Arrays.asList("1", "2", "3", "4", "5");

Stream<String> CollectionStream = collection.stream();

4. Stream of Arrays

To source a stream from an array, it can be executed like this,

Stream<String> Arraystream = Stream.of("1", "2", "3", "4", "5");

Streams can also be created from a specific part of an array:

String[] myArr = new String[]{"1", "2", "3", "4", "5"};

Stream<String> FullArrayStream = Arrays.stream(myArr);

Stream<String> PartArrayStream = Arrays.stream(myArr, 1, 3);

5. Stream of Primitive data

You can specifically create Java 8 streams for three primitive types in Java (int, long and double). As there is no way to use primitives as a type parameter with generics, there three interfaces are specifically used for primitive data types, IntStream, LongStream, and DoubleStream.

This is how you can use them in your code:

IntStream intStream = IntStream.range(1, 7);

LongStream longStream = LongStream.rangeClosed(1, 7);

DoubleStream doubleStream = LongStream.doubles(5);

The range() method used in the example creates a sequenced stream from the first argument to the second argument incrementing the value of subsequent elements with the step of 1. The result does not include the last parameter as it is just an upper bound of the sequence. The rangeClosed() method on the other hand does the same thing but with the inclusion of the second parameter in the range.

6. Stream of String type

the chars() method of the String class is used to create Java 8 streams sourced by a string.

See the following example where a string is separated according to a specified regex:

IntStream streamOfChars = "xyz".chars();

Stream<String> streamOfString =

Pattern.compile(", ").splitAsStream("x, y, z");

Not to be confused with the use of the IntStream interface here, as there is no interface for CharStream in Java, we use the IntStream to represent a stream of chars instead.

7. Stream of a File

The Java.nio.file package allows generating a Stream<String> from a text file using the lines() method. Each line of the text file is made into an element of the stream.

See this example below:

Path path = Paths.get("D:\\myFile.txt");

Stream<String> StringsStream = Files.lines(path);

Stream<String> CharsetStream =

Files.lines(path, Charset.forName("UTF-8"));

Java Stream Operations

So far, we have discussed the ways to create a stream. Now Let’s explore some common usages and operations that can be performed using the Java 8 streams.

1. forEach()

forEach() is the most commonly used operation. It loops over the stream elements and calls the supplied function on each element. forEach() is also a terminal operation, which means that when the operation is done, the stream pipeline can no longer be used.

See this simple example below demonstrating the forEach method:

public void usingForEachExample() {

stdList.stream().forEach(s -> s.promoteGrade());

assertThat(stdList, contains(

hasProperty("grade", equalTo(9)),

hasProperty("grade", equalTo(10)),

));

}

This will call the promoteGrade() method on each element in the stdList that matches the mentioned properties.

2. Map()

The Stream map(Function mapper) returns a stream consisting of the results of applying the given function to the elements from the original stream. The newly created stream can also be of any data type.

The example below converts a stream of Strings into a stream of Students:

public void usingMapsExample() {

String[] stdIds = { “CS04”, “CS05”, “CS06”, “CS07”, “CS08” };

List<Students> students = Stream.of(stdIds)

.map(studentRepository::findById)

.collect(Collectors.toList());

assertEquals(students.size(), stdIds.length);

}

In the above-mentioned code, a string stream of student ids is created from an array. Each String is then passed to the mapper function employeeRepository::findById(). The function will return the corresponding Student object that will form a Student stream.

3. Collect()

This method is used to get the data out from a stream once all the processing is done. It is done by performing the mutable fold operations on data elements held in the stream.

See the example below, a toList collector is used to collect all the elements from a stream into a List instance:

public void usingCollentExample () {

List<Student> students = stdList.stream().collect(Collectors.toList());

assertEquals(stdList, students);

}

4. Filter()

The filter() method produces a new stream that consists of the filtered elements from the original stream that fulfils certain criteria.

This is how it works:

public void usingFilterExample() {

String[] stdIds = { “CS04”, “CS05”, “CS06”, “CS07”, “CS08” };

List<Student> students = Stream.of(stdIds)

.map(studentRepository::findById)

.filter(s -> s != null)

.filter(s -> s.getMarks() > 90)

.collect(Collectors.toList());

}

In this example, we used the filter method to filter out the null references for invalid student ids and then again applied the method to filter out and just keep the students with more than 90 marks.

5. FindFirst()

The findFirst() method returns an Optional object for the first entry in the stream.

The optional can also be empty as the orElse() method is used with a null parameter:

public void usingFindFirstExample() {

String[] stdIds = { “CS04”, “CS05”, “CS06”, “CS07”, “CS08” };

Student student = Stream.of(stdIds)

.map(studentRepository::findById)

.filter(s -> s != null)

.filter(s -> s.getMarks() > 90)

.findFirst()

.orElse(null);

assertEquals(student.getMarks(), new Double(90));

}

In the above example, the first student with greater than 90 marks will be returned and if no student has scored greater than 90 marks then null will be returned.

6. ToArray()

Similar to the collect() method, the toArray() method is used to get data out from a stream but it is used to transfer the elements of a stream directly into an array.

public void usingToArrayExample() {
Student[] students = stdList.stream().toArray(Student[]::new);
assertThat(stdList.toArray(), equalTo(students));
}

The line Employee[]::new creates an empty array of Students which is then filled with the elements from the stream.

7. FlatMap()

A stream can also hold data from complex structures like a list consisting of lists of strings. In such cases, the flatMap() method is used to flatten the data structures to easily access the data. It can convert the Stream<List<String>> to a simpler Stream<String>.

public void UsingFlatMapExample() {

List<List<String>> nestedNames = Arrays.myList(

Arrays.myList("Shaharyar", "Lalani"),

Arrays.myList("James", "Gunns"),

Arrays.myList("Milly", "Hopkins"));

List<String> namesFlatStream = nestedNames.stream()

.flatMap(Collection::stream)

.collect(Collectors.toList());

}

Conclusion

We discussed how Java 8 stream works, how we can create a stream and several operations used by streams. This article aimed to give you a basic understanding and working knowledge of Java 8 streams. We have just scratched the surface as there are numerous more features offered by Java 8 streams such as parallel streams, pipelining, stream reduction, stream referencing and much more. You can explore these features further as per your requirements and level of interest.

The Java 8 streams prove to be a very powerful and easy-to-use tool for processing the sequence of elements. If you utilize it up to its full potential, Java 8 streams can make your code concise and can significantly improve the productivity of your application.

Also Read: Generate Random String in Java

new Java jobs

Author

Shaharyar Lalani is a developer with a strong interest in business analysis, project management, and UX design. He writes and teaches extensively on themes current in the world of web and app development, especially in Java technology.

Write A Comment