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

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

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

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

  • The Complete Guide to Stream API and Collectors in Java 8
  • Anatomy of Sequential Data Processing With Java Streams
  • Bridge the Gap of Zip Operation
  • Thread-Safety Pitfalls in XML Processing

Trending

  • Concourse CI/CD Pipeline: Webhook Triggers
  • Hybrid Cloud vs Multi-Cloud: Choosing the Right Strategy for AI Scalability and Security
  • AI’s Role in Everyday Development
  • Streamlining Event Data in Event-Driven Ansible
  1. DZone
  2. Coding
  3. Java
  4. Become a Master of Java Streams, Part 2: Intermediate Operations

Become a Master of Java Streams, Part 2: Intermediate Operations

Want to become a Java Streams Master?

By 
Per-Åke Minborg user avatar
Per-Åke Minborg
·
Julia Gustafsson user avatar
Julia Gustafsson
·
Oct. 16, 19 · Tutorial
Likes (26)
Comment
Save
Tweet
Share
31.2K Views

Join the DZone community and get the full member experience.

Join For Free

Immediate operations in Java streams

Want to become a Java Streams Master?

Just like a magic wand, an Intermediate operation transforms a Stream into another Stream. These operations can be combined in endless ways to perform anything from simple to highly complex tasks in a readable and efficient manner.

This article is the second out of five, complemented by a GitHub repository, containing instructions and exercises to each unit.

  • Part 1: Creating Streams
  • Part 2: Intermediate Operations
  • Part 3: Terminal Operations
  • Part 4: Database Streams
  • Part 5: Creating a Database Application Using Streams
You may also like: A Guide to Streams: In-Depth Tutorial With Examples

Intermediate Operations

Intermediate operations act as a declarative (functional) description of how elements of the Stream should be transformed. Together, they form a pipeline through which the elements will flow. What comes out at the end of the line naturally depends on how the pipeline is designed.

As opposed to a mechanical pipeline, an intermediate operation in a Stream pipeline may(*) render a new Stream that may depend on elements from previous stages. In the case of a map-operation (which we will introduce shortly), the new Stream might even contain elements of a different type.

(*) Strictly speaking, an intermediate operation is not mandated to create a new Stream. Instead, it can update its internal state or, if the intermediate operation did not change anything (such as.skip(0)) return the existing Stream from the previous stage.

To get a glimpse of what a pipeline can look like, recall the example used in the previous article:

List<String> list = Stream.of("Monkey", "Lion", "Giraffe","Lemur")
    .filter(s -> s.startsWith("L"))
    .map(String::toUpperCase)
    .sorted()
    .collect(toList());

System.out.println(list);
 [LEMUR, LION]


We will now go on to explain the meaning of these and other operations in more detail.

Filter

Based on our experience, filter() is one of the most useful operations of the Stream API. It enables you to narrow down a Stream to elements that fit certain criteria. Such criteria must be expressed as a Predicate (a function resulting in a boolean value), e.g. a lambda. The intention of the code below is to find the Strings that start with the letter "L" and discard the others.

 Stream<String> startsWithT = Stream.of(
   "Monkey", "Lion", "Giraffe", "Lemur"
)

    .filter(s -> s.startsWith("L"));
startsWithT: [Lion, Lemur]


Limit

There are some very simple, yet powerful, operations that provide a way to select or discard elements based on their position in the Stream. The first of these operations is limit(n), which basically does what it says — it creates a new stream that only contains the first n elements of the stream it is applied on. The example below illustrates how a Stream of four animals is shortened to only "Monkey" and "Lion".

Stream<String> firstTwo = Stream.of(
   "Monkey", "Lion", "Giraffe", "Lemur"
)
   .limit(2);
firstTwo: [Monkey, Lion]


Skip

Similarly, if we are only interested in some of the elements down the line, we can use the .skip(n)operation. If we apply skip(2) to our Stream of animals, we are left with the tailing two elements "Giraffe" and "Lemur".

Stream<String> firstTwo = Stream.of(
   "Monkey", "Lion", "Giraffe", "Lemur"
)
   .skip(2);
lastTwo: [Giraffe, Lemur]


Distinct

There are also situations where we only need one occurrence of each element of the Stream. Rather than having to filter out any duplicates manually, a designated operation exists for this purpose — distinct(). It will check for equality using Object::equals and returns a new Stream with only unique elements. This is akin to a Set.

Stream<String> uniqueAnimals = Stream.of(
   "Monkey", "Lion", "Giraffe", "Lemur", "Lion"
)
   .distinct();
uniqueAnimals: ["Monkey", "Lion", "Giraffe", "Lemur"]


Sorted

Sometimes, the order of the elements is important, in which case we want control over how things are ordered. The simplest way to do this is with the sorted-operation, which will arrange the elements in the natural order. In the case of the Strings below, that means alphabetical order.

Stream<String> alphabeticOrder = Stream.of(
    "Monkey", "Lion", "Giraffe", "Lemur"
)
   .sorted();
alphabeticOrder: [Giraffe, Lemur, Lion, Monkey]


Sorted With Comparator

Just having the option to sort in natural order can be a bit limiting sometimes. Luckily, it is possible to apply a custom Comparator to inspect a certain property of the element. We could for example order the Strings after their lengths accordingly:

Stream<String> lengthOrder = Stream.of(
    "Monkey", "Lion", "Giraffe", "Lemur"
)
   .sorted(Comparator.comparing(String::length));
lengthOrder: [Lion, Lemur, Monkey, Giraffe]


Map

One of the most versatile operations we can apply to a Stream is map(). It allows elements of a Stream to be transformed into something else by mapping them to another value or type. This means the result of this operation can be a Stream of any type R. The example below performs a simple mapping from String to String, replacing any capital letters with their lower case equivalent.

Stream<String> lowerCase = Stream.of(
    "Monkey", "Lion", "Giraffe", "Lemur"
)
   .map(String::toLowerCase);
lowerCase: [monkey, lion, giraffe, lemur]


Map to Integer, Double or Long

There are also three special implementations of the map-operation, which are limited to mapping elements to the primitive types int, double, and long.

.mapToInt();
.mapToDouble();
.mapToLong();


Hence, the result of these operations always corresponds to an IntStream, DoubleStream, or LongStream. Below, we demonstrate how .mapToInt() can be used to map our animals to the length of their names:

IntStream lengths = Stream.of(
    "Monkey", "Lion", "Giraffe", "Lemur"
)
   .mapToInt(String::length);
lengths: [6, 4, 7, 5]

Note: String::length is the equivalent of the lambda s -> s.length(). We prefer the former notation since it makes the code more concise and readable.

FlatMap

The last operation that we will cover in this article might be more tricky to understand even though it can be quite powerful. It is related to the map() operation but instead of taking a Function that goes from a type T to a return type R, it takes a Function that goes from a type T and returns a Stream of R. These "internal" streams are then flattened out to the resulting streams, resulting in a concatenation of all the elements of the internal streams.

Stream<Character> chars = Stream.of(
    "Monkey", "Lion", "Giraffe", "Lemur"
)
    .flatMap(s -> s.chars().mapToObj(i -> (char) i));
chars: [M, o, n, k, e, y, L, i, o, n, G, i, r, a, f, f, e, L, e, m, u, r]

Exercises

If you haven't already cloned the associated GitHub repo, we encourage you to do so now. The content of this article is sufficient to solve the second unit which is called MyUnit2Intermediate. The corresponding Unit2Intermediate Interface contains JavaDocs that describe the intended implementation of the methods in MyUnit2MyIntermediate.

public interface Unit2Intermediate {
   /**
    * Return a Stream that contains words that are
    * longer than three characters. Shorter words
    * (i.e. words of length 0, 1, 2 and 3)
    * shall be filtered away from the stream.
    * <p>
    *  A Stream of
    *      ["The", "quick", "quick", "brown", "fox",
    *      "jumps", "over", "the", "lazy", "dog"]
    *  would produce a Stream of the elements
    *      ["quick", "quick", "brown", "jumps",
    *      "over", "lazy"]
    */

   Stream<String> wordsLongerThanThreeChars(Stream<String> stream);

The provided tests (e.g. Unit2MyIntermediateTest) will act as an automatic grading tool, allowing you to know if your solution was correct or not.

tests with automatic grading tool

Next Article

In the next article, we proceed to terminal operations and explore how we can collect, count, and/or group the resulting elements of our pipeline.

Until then, happy coding!

Resources

GitHub Repository"hol-streams"
Speedment Stream ORM Initializer

Further Reading

A Guide to Streams: In-Depth Tutorial With Examples

A Java 8 Streams Cookbook

Think Twice Before Using Java 8 Parallel 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) Java (programming language) Element master

Published at DZone with permission of Per-Åke Minborg, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • The Complete Guide to Stream API and Collectors in Java 8
  • Anatomy of Sequential Data Processing With Java Streams
  • Bridge the Gap of Zip Operation
  • Thread-Safety Pitfalls in XML Processing

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!