DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Java Stream API: 3 Things Every Developer Should Know About
  • Optimizing Java Applications: Parallel Processing and Result Aggregation Techniques
  • Functional Approach To String Manipulation in Java
  • Techniques You Should Know as a Kafka Streams Developer

Trending

  • Teradata Performance and Skew Prevention Tips
  • AI's Dilemma: When to Retrain and When to Unlearn?
  • Rust and WebAssembly: Unlocking High-Performance Web Apps
  • Debugging Core Dump Files on Linux - A Detailed Guide
  1. DZone
  2. Data Engineering
  3. Databases
  4. Overview of Java Stream API Extensions

Overview of Java Stream API Extensions

Check out the top Java Stream API extensions offered by popular Java libraries.

By 
Piotr Mińkowski user avatar
Piotr Mińkowski
·
Oct. 28, 19 · Presentation
Likes (6)
Comment
Save
Tweet
Share
33.1K Views

Join the DZone community and get the full member experience.

Join For Free

Java Stream API Extensions

Check out this overview of Java Stream API extensions

Stream API, introduced in Java 8, is probably still the most important new feature that has been included in Java over the last several years. I think every Java developer has an opportunity to use the Java Stream API in their career. Or, I should say that you should probably use it on a day-to-day basis.

However, if you compare the built-in features offered for functional programming with some other languages — for example, Kotlin — you will quickly realize that the number of methods provided by the Stream API is very limited. Therefore, the community has created several libraries used just for extending the API offered by pure Java. Today, I'm going to show the most interesting Stream API extensions offered by the three popular Java libraries: StreamEx, jOOλ, and Guava.

This article treats only about sequential Streams. If you would use parallel Streams, you won't be able to leverage jOOλ since it is dedicated only for sequential Streams.

You may also like: Java 8 Streams: In-Depth Tutorial With Examples

Dependencies

Here's the list of current releases for all three libraries compared in this article.

<dependencies>
<dependency>
<groupId>one.util</groupId>
<artifactId>streamex</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jool</artifactId>
<version>0.9.13</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.1-jre</version>
</dependency>
</dependencies>


1. Zipping

When working with Java Streams in more advanced applications, you will often process multiple streams. Also, they can often contain different objects. One of the useful operations, in that case, is zipping. Zipping operation returns a stream that contains a pair of corresponding elements in given two streams, which means that they are in the same position in those streams.

Let's consider two objects Person and PersonAddress. Assuming we have two streams, first which contains only Person objects, second with PersonAddress objects, and the order of elements clearly indicate their association, we may zip them to create a new stream of objects containing all the fields from Person and PersonAddress. Here's the screen that illustrates the described scenario.
Zipping demonstrationZipping is supported by all the three currently described libraries. Let's begin with a Guava example. It provides the only one method dedicated for zipping — static zip method that takes three parameters: first stream, second stream, and the mapping function.

Stream<Person> s1 = Stream.of(
new Person(1, "John", "Smith"),
new Person(2, "Tom", "Hamilton"),
new Person(3, "Paul", "Walker")
);
Stream<PersonAddress> s2 = Stream.of(
new PersonAddress(1, "London", "Street1", "100"),
new PersonAddress(2, "Manchester", "Street1", "101"),
new PersonAddress(3, "London", "Street2", "200")
);
Stream<PersonDTO> s3 = Streams.zip(s1, s2, (p, pa) -> PersonDTO.builder()
.id(p.getId())
.firstName(p.getFirstName())
.lastName(p.getLastName())
.city(pa.getCity())
.street(pa.getStreet())
.houseNo(pa.getHouseNo()).build());
s3.forEach(dto -> {
Assertions.assertNotNull(dto.getId());
Assertions.assertNotNull(dto.getFirstName());
Assertions.assertNotNull(dto.getCity());
});


Both StreamEx and jOOλ offers more possibilities for zipping than Guava. We can between some static methods or non-static methods invoked on a given stream. Let's take a look at how we may perform it using the StreamEx zipWith method.

StreamEx<Person> s1 = StreamEx.of(
new Person(1, "John", "Smith"),
new Person(2, "Tom", "Hamilton"),
new Person(3, "Paul", "Walker")
);
StreamEx<PersonAddress> s2 = StreamEx.of(
new PersonAddress(1, "London", "Street1", "100"),
new PersonAddress(2, "Manchester", "Street1", "101"),
new PersonAddress(3, "London", "Street2", "200")
);
StreamEx<PersonDTO> s3 = s1.zipWith(s2, (p, pa) -> PersonDTO.builder()
.id(p.getId())
.firstName(p.getFirstName())
.lastName(p.getLastName())
.city(pa.getCity())
.street(pa.getStreet())
.houseNo(pa.getHouseNo()).build());
s3.forEach(dto -> {
Assertions.assertNotNull(dto.getId());
Assertions.assertNotNull(dto.getFirstName());
Assertions.assertNotNull(dto.getCity());
});


The example is almost identical. We have a zip method called on a given stream.

Seq<Person> s1 = Seq.of(
new Person(1, "John", "Smith"),
new Person(2, "Tom", "Hamilton"),
new Person(3, "Paul", "Walker"));
Seq<PersonAddress> s2 = Seq.of(
new PersonAddress(1, "London", "Street1", "100"),
new PersonAddress(2, "Manchester", "Street1", "101"),
new PersonAddress(3, "London", "Street2", "200"));
Seq<PersonDTO> s3 = s1.zip(s2, (p, pa) -> PersonDTO.builder()
.id(p.getId())
.firstName(p.getFirstName())
.lastName(p.getLastName())
.city(pa.getCity())
.street(pa.getStreet())
.houseNo(pa.getHouseNo()).build());
s3.forEach(dto -> {
Assertions.assertNotNull(dto.getId());
Assertions.assertNotNull(dto.getFirstName());
Assertions.assertNotNull(dto.getCity());
});


2. Joining

Zipping operations merge elements from two different streams in accordance with their order in those streams. What if we would like to associate elements basing on their fields like id, but not the order in a stream? Something ala LEFT JOIN or RIGHT JOIN between two entities. The result of an operation should be the same as for the previous section — a new stream of objects containing all the fields from Person and PersonAddress. The described operation is illustrated in the picture below.
Joining demonstration
When it comes to a join operation, only jOOλ provides some methods for that. Since it is dedicated to object-oriented queries, we may choose between many join options. For example, there are innerJoin, leftOuterJoin, rightOuterJoin, and crossJoin methods. In the source code visible below, you can see an example of innerJoin usage. This method takes two parameters: stream to join and predicate for matching elements from the first stream and joining stream. If we would like to create new object based on the innerJoin result, we should additionally invoke map operation.

Seq<Person> s1 = Seq.of(
new Person(1, "John", "Smith"),
new Person(2, "Tom", "Hamilton"),
new Person(3, "Paul", "Walker"));
Seq<PersonAddress> s2 = Seq.of(
new PersonAddress(2, "London", "Street1", "100"),
new PersonAddress(3, "Manchester", "Street1", "101"),
new PersonAddress(1, "London", "Street2", "200"));
Seq<PersonDTO> s3 = s1.innerJoin(s2, (p, pa) -> p.getId().equals(pa.getId())).map(t -> PersonDTO.builder()
.id(t.v1.getId())
.firstName(t.v1.getFirstName())
.lastName(t.v1.getLastName())
.city(t.v2.getCity())
.street(t.v2.getStreet())
.houseNo(t.v2.getHouseNo()).build());
s3.forEach(dto -> {
Assertions.assertNotNull(dto.getId());
Assertions.assertNotNull(dto.getFirstName());
Assertions.assertNotNull(dto.getCity());
});


3. Grouping

The next useful operation that is supported by Java Stream API only through the static method groupingBy in java.util.stream.Collectors is grouping ( s1.collect(Collectors.groupingBy(PersonDTO::getCity))). As a result of executing such an operation on stream, you get a map with keys are the values resulting from applying the grouping function to the input elements, and whose corresponding values are lists containing the input elements. This operation is some kind of aggregation, so you get java.util.List as a result, no java.util.stream.Stream.

Both StreamEx and jOOλ provide methods for grouping streams. Let's start with the StreamEx groupingBy operation example. Assuming we have an input stream of PersonDTO objects, we will group them by person's home city.

StreamEx<PersonDTO> s1 = StreamEx.of(
PersonDTO.builder().id(1).firstName("John").lastName("Smith").city("London").street("Street1").houseNo("100").build(),
PersonDTO.builder().id(2).firstName("Tom").lastName("Hamilton").city("Manchester").street("Street1").houseNo("101").build(),
PersonDTO.builder().id(3).firstName("Paul").lastName("Walker").city("London").street("Street2").houseNo("200").build(),
PersonDTO.builder().id(4).firstName("Joan").lastName("Collins").city("Manchester").street("Street2").houseNo("201").build()
);
Map<String, List<PersonDTO>> m = s1.groupingBy(PersonDTO::getCity);
Assertions.assertNotNull(m.get("London"));
Assertions.assertTrue(m.get("London").size() == 2);
Assertions.assertNotNull(m.get("Manchester"));
Assertions.assertTrue(m.get("Manchester").size() == 2);


The result of similar jOOλ groupBy method is the same. It also returns multiple java.util.List objects inside map.

Seq<PersonDTO> s1 = Seq.of(
PersonDTO.builder().id(1).firstName("John").lastName("Smith").city("London").street("Street1").houseNo("100").build(),
PersonDTO.builder().id(2).firstName("Tom").lastName("Hamilton").city("Manchester").street("Street1").houseNo("101").build(),
PersonDTO.builder().id(3).firstName("Paul").lastName("Walker").city("London").street("Street2").houseNo("200").build(),
PersonDTO.builder().id(4).firstName("Joan").lastName("Collins").city("Manchester").street("Street2").houseNo("201").build()
);
Map<String, List<PersonDTO>> m = s1.groupBy(PersonDTO::getCity);
Assertions.assertNotNull(m.get("London"));
Assertions.assertTrue(m.get("London").size() == 2);
Assertions.assertNotNull(m.get("Manchester"));
Assertions.assertTrue(m.get("Manchester").size() == 2);


4. Multiple Concatenation

That's a pretty simple scenario. Java Stream API provides a static method for concatenation, but only for two streams. Sometimes, it is convenient to concat multiple streams in a single step. Guava and jOOλ provide a dedicated method for that.

Here's the example of calling concat method with jOOλ:


Seq<Integer> s1 = Seq.of(1, 2, 3);
Seq<Integer> s2 = Seq.of(4, 5, 6);
Seq<Integer> s3 = Seq.of(7, 8, 9);
Seq<Integer> s4 = Seq.concat(s1, s2, s3);
Assertions.assertEquals(9, s4.count());


And here's a similar example for Guava:

Stream<Integer> s1 = Stream.of(1, 2, 3);
Stream<Integer> s2 = Stream.of(4, 5, 6);
Stream<Integer> s3 = Stream.of(7, 8, 9);
Stream<Integer> s4 = Streams.concat(s1, s2, s3);
Assertions.assertEquals(9, s4.count());


5. Partitioning

The partitioning operation is very similar to grouping, but divides input stream into two lists or streams where elements in the first list fulfill a given predicate, while elements in the second list do not.
The StreamEx partitioningBy method will return two List objects inside Map.

StreamEx<PersonDTO> s1 = StreamEx.of(
PersonDTO.builder().id(1).firstName("John").lastName("Smith").city("London").street("Street1").houseNo("100").build(),
PersonDTO.builder().id(2).firstName("Tom").lastName("Hamilton").city("Manchester").street("Street1").houseNo("101").build(),
PersonDTO.builder().id(3).firstName("Paul").lastName("Walker").city("London").street("Street2").houseNo("200").build(),
PersonDTO.builder().id(4).firstName("Joan").lastName("Collins").city("Manchester").street("Street2").houseNo("201").build()
);
Map<Boolean, List<PersonDTO>> m = s1.partitioningBy(dto -> dto.getStreet().equals("Street1"));
Assertions.assertTrue(m.get(true).size() == 2);
Assertions.assertTrue(m.get(false).size() == 2);


In opposition to StreamEx, jOOλ is returning two streams (Seq) inside the Tuple2 object. This approach has one big advantage over StreamEx — you can still invoke stream operations on a result without any conversions.

Seq<PersonDTO> s1 = Seq.of(
PersonDTO.builder().id(1).firstName("John").lastName("Smith").city("London").street("Street1").houseNo("100").build(),
PersonDTO.builder().id(2).firstName("Tom").lastName("Hamilton").city("Manchester").street("Street1").houseNo("101").build(),
PersonDTO.builder().id(3).firstName("Paul").lastName("Walker").city("London").street("Street2").houseNo("200").build(),
PersonDTO.builder().id(4).firstName("Joan").lastName("Collins").city("Manchester").street("Street2").houseNo("201").build()
);
Tuple2<Seq<PersonDTO>, Seq<PersonDTO>> t = s1.partition(dto -> dto.getStreet().equals("Street1"));
Assertions.assertTrue(t.v1.count() == 2);
Assertions.assertTrue(t.v2.count() == 2);


6. Aggregation

Only jOOλ provides methods for stream aggregation. For example, we can count sum, avg, or median. Since jOOλ is a part of jOOQ, it is targeted to be used for object-oriented queries, and in fact, it provides many operations that correspond to the SQL SELECT clauses.

The fragment of source code visible below illustrates how easily we can count a sum of the selected field in the stream of objects, on the example of all the person's age.

Seq<Person> s1 = Seq.of(
new Person(1, "John", "Smith", 35),
new Person(2, "Tom", "Hamilton", 45),
new Person(3, "Paul", "Walker", 20)
);
Optional<Integer> sum = s1.sum(Person::getAge);
Assertions.assertEquals(100, sum.get());


7. Pairing

StreamEx allows you process pairs of adjacent objects in the stream and apply a given function on them. It may be achieved by using pairMap function. In the fragment of code visible below, I'm counting the sum for each pair of adjacent numbers in the stream.

StreamEx<Integer> s1 = StreamEx.of(1, 2, 1, 2, 1);
StreamEx<Integer> s2 = s1.pairMap(Integer::sum);
s2.forEach(i -> Assertions.assertEquals(3, i));


Summary

While Guava Streams is just a part of bigger Google's library, StreamEx and jOOλ are strictly dedicated to lambda streams. In comparison to other libraries described in this article, jOOλ provides the largest number of features and operations.

If you are looking for a library that helps you in performing OO operations on streams, jOOλ is definitely for you. Unlike the other, it provides operations, for example for joining or aggregation. StreamEx also provides many useful operations for manipulating the streams. It is not related to object-oriented queries and SQL, so you won't find their methods for out of order joins or aggregation — which does not change the fact that it is very useful and worth recommending the library.

Guava provides relatively small number of features for streams. However, if you have already used it in your application, it could a nice addition to manipulating the streams. The source code snippets with examples of usage may be found on this GitHub repository.

Further Reading

Java 8 Streams: In-Depth Tutorial With Examples

Process Collections Easily in Java 8 Streams



If you enjoyed this article and want to learn more about Java Streams, check out this collection of tutorials and articles on all things Java Streams.

Stream (computing) API Java (programming language)

Published at DZone with permission of Piotr Mińkowski, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Java Stream API: 3 Things Every Developer Should Know About
  • Optimizing Java Applications: Parallel Processing and Result Aggregation Techniques
  • Functional Approach To String Manipulation in Java
  • Techniques You Should Know as a Kafka Streams Developer

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!