Java Stream API Tutorial

1. Overview of Stream API

Java Stream API is a powerful tool for processing collections of data. It was introduced in Java 8 and allows developers to perform operations on data in a functional way. Java Stream API is used for filtering, transforming, and aggregating data in a concise and expressive manner. This can help make your code more readable and maintainable.

1.1 How to create a Stream of Integers?

To create a stream of integers, we can use the IntStream class provided by Java Stream API. Here’s an example:

IntStream stream = IntStream.of(1, 2, 3, 4, 5);
stream.forEach(System.out::println);

In this example, we create a stream of integers with the of method and pass in the values we want to include in the stream. We then use the forEach method to print out each integer in the stream.

1.2 How to Stream a Collection of Strings

To stream a collection of strings, we can use the Stream class provided by Java Stream API. Here’s an example:

List<String> countries = Arrays.asList("USA", "China", "Russia", "India", "Brazil");
Stream<String> stream = countries.stream();
stream.forEach(System.out::println);

In this example, we create a List of countries and then create a stream of strings using the stream method on the List. We then use the forEach method to print out each country in the stream.

2. What are the Core Stream API Operations?

Stream operations can be divided into three categories:

  • Intermediate operations: Intermediate operations are operations that produce a new stream as their result. These operations can be chained together to form a pipeline of operations that is executed in a lazy manner. Intermediate operations do not produce any result until a terminal operation is called.
  • Terminal operations: Terminal operations are operations that produce a non-stream result. These operations trigger the pipeline of intermediate operations to be executed.
  • Short-circuit operations: Short-circuit operations are a special type of terminal operation that can terminate the execution of the pipeline early.

3. Intermediate Stream Operations

Intermediate operations can be used to filter, transform, or sort the data in a stream.

3.1 filter()

The filter operation can be used to create a new stream that includes only the elements that meet a certain condition. Here’s an example:

List<String> countries = Arrays.asList("USA", "China", "Russia", "India", "Brazil");
Stream<String> filteredStream = countries.stream().filter(country -> country.startsWith("R"));
filteredStream.forEach(System.out::println);

In this example, we use the filter operation to create a new stream that includes only the countries that start with the letter “R”. We then use the forEach method to print out each country in the filtered stream.

3.2 map()

The map operation can be used to create a new stream that contains the result of applying a function to each element in the original stream. Here’s an example:

List<String> countries = Arrays.asList("USA", "China", "Russia", "India", "Brazil");
Stream<Integer> lengths = countries.stream().map(String::length);
lengths.forEach(System.out::println);

In this example, we use the map operation to create a new stream that contains the length of each country name. We then use the forEach method to print out each length in the new stream. More about map() operation.

3.3 sorted()

The sorted() method returns a stream consisting of the elements of this stream, sorted according to natural order or by a specified comparator.

In the following example, we create a stream of countries and sort them in ascending order based on their names using the sorted() method:

List<String> countries = Arrays.asList("France", "Spain", "Portugal", "Italy", "Germany", "United Kingdom");
Stream<String> sortedCountries = countries.stream().sorted();
sortedCountries.forEach(country -> System.out.println(country));

Output:

France
Germany
Italy
Portugal
Spain
United Kingdom

In this example, we first create a list of countries and then create a stream from the list using the stream() method. We then use the sorted() method to sort the countries in natural order, which is ascending order based on their names. Finally, we use the forEach() method to print the sorted countries to the console.

We can also sort the countries in descending order by passing a comparator to the sorted() method, as shown in the following example:

List<String> countries = Arrays.asList("France", "Spain", "Portugal", "Italy", "Germany", "United Kingdom");
Stream<String> sortedCountries = countries.stream().sorted(Comparator.reverseOrder());
sortedCountries.forEach(country -> System.out.println(country));

Output:

United Kingdom
Spain
Portugal
Italy
Germany
France

In this example, we pass the reverseOrder() method to the sorted() method to sort the countries in descending order. The reverseOrder() method returns a comparator that imposes the reverse of the natural ordering of a collection of objects.

That’s it for the sorted() method. We have now covered all the intermediate operations. Next, we will cover the terminal operations.

4. Terminal Operations

Terminal operations are operations that are called on a stream once and then the stream is closed. These operations produce a non-stream result, such as a value or a collection. They are the final operation that is performed on a stream. Here are some commonly used terminal operations in Java Stream:

4.1. forEach()

The forEach() method is used to perform an action for each element of a stream. The method takes a Consumer functional interface as an argument, which specifies the action to be performed on each element. The forEach() method does not return any result.

Here’s an example of using the forEach() method to print out each element of a stream:

List<String> countries = Arrays.asList("USA", "Canada", "Mexico", "Brazil", "Argentina", "Chile");
Stream<String> countryStream = countries.stream();

// Print out each element of the stream
countryStream.forEach(country -> System.out.println(country));

Output:

USA
Canada
Mexico
Brazil
Argentina
Chile

4.2. collect()

The collect() method is used to accumulate the elements of a stream into a collection or other type of data structure. It takes a Collector functional interface as an argument, which specifies the way in which the elements should be collected. The collect() method returns the collected elements as a collection or other type of data structure.

Here’s an example of using the collect() method to collect the elements of a stream into a list:

List<String> countries = Arrays.asList("USA", "Canada", "Mexico", "Brazil", "Argentina", "Chile");
Stream<String> countryStream = countries.stream();

// Collect the elements of the stream into a list
List<String> countryList = countryStream.collect(Collectors.toList());

// Print out the collected list
System.out.println(countryList);

Output:

[USA, Canada, Mexico, Brazil, Argentina, Chile]

More about collect() operation.

4.3. match()

The match() method is used to check if any or all of the elements of a stream match a given condition. The method takes a Predicate functional interface as an argument, which specifies the condition to be matched. The match() method returns a boolean value that indicates whether any or all of the elements match the condition.

Here’s an example of using the match() method to check if any element of a stream contains the letter “x”:

List<String> countries = Arrays.asList("USA", "Canada", "Mexico", "Brazil", "Argentina", "Chile");
Stream<String> countryStream = countries.stream();

// Check if any element of the stream contains the letter "x"
boolean containsX = countryStream.anyMatch(country -> country.contains("x"));

// Print out the result
System.out.println(containsX);

Output:

true

4.4. count()

The count() method is used to count the number of elements in a stream. The method returns a long value that represents the number of elements in the stream.

Here’s an example of using the count() method to count the number of elements in a stream:

List<String> countries = Arrays.asList("USA", "Canada", "Mexico", "Brazil", "Argentina", "Chile");
Stream<String> countryStream = countries.stream();

// Count the number of elements in the stream
long count = countryStream.count();

// Print out the result
System.out.println(count);

Output:

6

More about count() operation.

4.5. reduce()

The reduce() method combines all elements in a stream into a single result. It takes a binary operator and an identity value as arguments and returns an Optional that contains the result of the reduction operation.

In the following example, we use the reduce() method to calculate the sum of all population figures of the countries in the stream:

List<Country> countries = getCountries();
Optional<Integer> totalPopulation = countries.stream()
    .map(Country::getPopulation)
    .reduce(Integer::sum);
if (totalPopulation.isPresent()) {
    System.out.println("Total population: " + totalPopulation.get());
} else {
    System.out.println("No countries in the stream.");
}

In the example above, we first get a stream of Country objects from the getCountries() method. We then use the map() method to convert each country into its population figure. Finally, we use the reduce() method with the Integer::sum method reference as the binary operator, which adds two numbers together, to calculate the sum of all the population figures. The result is wrapped in an Optional object, which we check before printing the total population to the console. If the result is present, we print the total population, and if it is not present, we print a message indicating that there are no countries in the stream.

This is just one example of how the reduce() method can be used. You can use it to perform any type of reduction operation on a stream, such as finding the minimum or maximum value, or concatenating strings together. The possibilities are endless.

More about reduce() operation.

5. Short-circuit operations

Short-circuit operations are used to terminate the Stream pipeline as soon as a certain condition is met. These operations can help to reduce the processing time of large data sets.

5.1 anyMatch()

The anyMatch() operation checks whether any element of the stream matches the given predicate. It returns a boolean value that indicates whether at least one element of the stream matches the given condition.

Example:

List<String> countries = Arrays.asList("USA", "Canada", "Mexico", "Brazil", "Argentina");

Stream<String> stream = countries.stream();
boolean anyMatch = stream.anyMatch(country -> country.startsWith("B"));

System.out.println(anyMatch); // Output: true

In this example, we create a stream from a list of countries and check whether any of them starts with the letter “B”. The anyMatch() operation returns true because the condition is met by the country “Brazil”.

More about anyMatch() operation.

5.2 findFirst()

The findFirst() operation returns the first element of the stream or an empty Optional if the stream is empty.

Example:

List<String> countries = Arrays.asList("USA", "Canada", "Mexico", "Brazil", "Argentina");

Stream<String> stream = countries.stream();
Optional<String> first = stream.filter(country -> country.startsWith("A")).findFirst();

if (first.isPresent()) {
    System.out.println(first.get()); // Output: Argentina
} else {
    System.out.println("No country found!");
}

In this example, we create a stream from a list of countries and filter only the countries that start with the letter “A”. The findFirst() operation returns the first country that meets this condition, which is “Argentina”. We use an Optional to handle the case where no country is found, which would result in an empty Optional.

More about findFirst() operation.

Frequently Asked Questions about Java Stream API

  1. What is the Java Stream API?
    The Java Stream API is a modern, functional way to process collections of objects in Java. It provides a way to express complex data processing queries as concise, readable, and reusable code.
  2. What are the benefits of using the Java Stream API?
    The Java Stream API offers a number of benefits, including more concise and readable code, improved performance, and better use of parallel processing.
  3. What types of collections can be processed using the Java Stream API?
    The Java Stream API can process any collection that implements the Collection interface, as well as arrays and other types of collections.
  4. What are intermediate operations in the Java Stream API?
    Intermediate operations are operations that are performed on a stream of data before a terminal operation is executed. They do not actually execute the stream processing but instead create a new stream that can be further processed.
  5. What are terminal operations in the Java Stream API?
    Terminal operations are operations that perform the actual stream processing and produce a result. Examples include forEach(), collect(), match(), count(), and reduce().
  6. Can the Java Stream API be used with parallel processing?
    Yes, the Java Stream API is designed to work with parallel processing. By default, streams are processed in a sequential manner, but the parallel() method can be used to enable parallel processing.
  7. Is the Java Stream API thread-safe?
    Yes, the Java Stream API is thread-safe. Streams are designed to be used in a multi-threaded environment, and operations performed on a stream are guaranteed to be thread-safe.
  8. How do I convert a stream back to a collection in the Java Stream API?
    To convert a stream back to a collection, you can use the collect() method. This method takes a Collector object that specifies how to accumulate the stream elements into a collection.

I hope this tutorial was helpful to you. To learn more, check out other tutorials about Functional Programming in Java.

Leave a Reply

Your email address will not be published. Required fields are marked *