Skip to main content Link Menu Expand (external link) Document Search Copy Copied

Building Stream Pipelines with Collections and Lambdas

Header Image

Ahoy mateys! As pirates, we are always searching for treasure and looking for the most efficient way to do it. In the world of programming, we often work with collections of data, and one of the most powerful tools for working with collections is the stream API. Stream pipelines allow us to chain together operations on collections using lambdas, making it easy to filter, map, and reduce our data to find the treasure we seek.

What is a Stream Pipeline?

A stream pipeline is a sequence of operations that we can perform on a collection of data. These operations can be either intermediate or terminal. Intermediate operations transform the data in some way and return a new stream, while terminal operations produce a result or a side-effect.

Building a Stream Pipeline with Collections and Lambdas

Let’s say we have a collection of pirate ships, and we want to find the ship with the most cannons. We can build a stream pipeline to help us find the ship we seek.

List<Ship> ships = Arrays.asList(
    new Ship("Black Pearl", 40),
    new Ship("Queen Anne's Revenge", 50),
    new Ship("Flying Dutchman", 30)
);

Ship largestShip = ships.stream()
    .max(Comparator.comparing(Ship::getCannons))
    .orElse(null);

System.out.println("The largest ship is " + largestShip.getName() + " with " + largestShip.getCannons() + " cannons.");

In this example, we start with our collection of pirate ships and call the stream() method to create a stream of the ships. We then use the max method to find the ship with the most cannons, passing in a lambda expression as the comparator to compare the ships by their cannons. Finally, we use the orElse method to return null if no ship is found.

Intermediate and Terminal Operations

Now that we have seen how to build a basic stream pipeline with lambdas, let’s take a closer look at the intermediate and terminal operations that we can use to transform and reduce our data.

Intermediate Operations

Intermediate operations transform the data in some way and return a new stream. Here are some common intermediate operations:

Filtering

ships.stream()
    .filter(ship -> ship.getCannons() >= 40)
    .forEach(ship -> System.out.println(ship.getName() + " has at least 40 cannons."));

This pipeline filters out any ship with fewer than 40 cannons and then prints the names of the remaining ships.

Mapping

ships.stream()
    .map(ship -> ship.getName())
    .forEach(name -> System.out.println(name));

This pipeline maps the collection of ships to a collection of their names and then prints the names of the ships.

Terminal Operations

Terminal operations produce a result or a side-effect. Here are some common terminal operations:

Reduction

int totalCannons = ships.stream()
    .mapToInt(Ship::getCannons)
    .sum();
System.out.println("Total cannons: " + totalCannons);

This pipeline maps the collection of ships to a collection of their cannons, sums them up, and prints the total number of cannons.

Iteration

ships.stream()
    .forEach(ship -> System.out.println(ship.getName() + " has " + ship.getCannons() + " cannons."));

This pipeline iterates over the collection of ships and prints the name and number of cannons of each ship.

Conclusion

Stream pipelines are a powerful tool for working with collections of data in Java. By using lambdas to chain together operations, we can easily filter, map, and reduce our data tofind the treasure we seek. With the ability to perform intermediate and terminal operations on our data, we have the power to manipulate our collections in a way that suits our needs.

As we have seen, intermediate operations such as filtering and mapping allow us to transform our data, while terminal operations such as reduction and iteration allow us to produce results or side-effects. By combining these operations, we can build powerful stream pipelines that can help us find the treasure we seek.

So, mateys, set sail and put these stream pipelines to good use! With the power of collections and lambdas, you will surely find the treasure you seek. Until next time, happy coding and may the winds of fortune be ever in your favor.

Intermediate and Terminal Operations in Stream Pipelines (continued)

Intermediate Operations (continued)

FlatMapping

List<List<String>> words = Arrays.asList(
    Arrays.asList("the", "quick", "brown"),
    Arrays.asList("fox", "jumps", "over"),
    Arrays.asList("the", "lazy", "dog")
);
List<String> flattenedWords = words.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toList());
System.out.println(flattenedWords);

This pipeline takes a nested list of words and flattens it into a single list of words.

Sorting

ships.stream()
    .sorted(Comparator.comparing(Ship::getCannons))
    .forEach(ship -> System.out.println(ship.getName() + " has " + ship.getCannons() + " cannons."));

This pipeline sorts the collection of ships by their cannons and then prints the name and number of cannons of each ship.

Terminal Operations (continued)

Matching

boolean anyShipHasMoreThan40Cannons = ships.stream()
    .anyMatch(ship -> ship.getCannons() > 40);
System.out.println("Any ship has more than 40 cannons? " + anyShipHasMoreThan40Cannons);

This pipeline checks if any ship in the collection has more than 40 cannons and prints the result.

Collecting

List<String> shipNames = ships.stream()
    .map(Ship::getName)
    .collect(Collectors.toList());
System.out.println("Ship names: " + shipNames);

This pipeline maps the collection of ships to a collection of their names and collects them into a new list, which is then printed.

Reducing with Binary Operator

Optional<Integer> totalCannons = ships.stream()
    .map(Ship::getCannons)
    .reduce((c1, c2) -> c1 + c2);
System.out.println("Total cannons: " + totalCannons.orElse(0));

This pipeline maps the collection of ships to a collection of their cannons and reduces them to a single value by adding them together. The result is an Optional that is printed if present.

Conclusion

Stream pipelines with lambdas are a powerful tool for working with collections of data in Java. By using intermediate and terminal operations, we can easily transform and reduce our data to find the treasure we seek. It’s important to note that stream pipelines can be parallelized for even greater performance gains, but that’s a tale for another day. Keep on coding, pirates!

Combining Stream Operations with Lambdas

One of the most powerful features of stream pipelines is the ability to chain together multiple operations to create complex transformations of our data. This allows us to perform advanced analysis and manipulation of our collections with just a few lines of code.

Chaining Intermediate Operations

List<String> shipNamesWithMoreThan30Cannons = ships.stream()
    .filter(ship -> ship.getCannons() > 30)
    .sorted(Comparator.comparing(Ship::getName))
    .map(Ship::getName)
    .collect(Collectors.toList());
System.out.println("Ship names with more than 30 cannons: " + shipNamesWithMoreThan30Cannons);

In this example, we filter out any ship with fewer than 30 cannons, sort the remaining ships by name, map the collection of ships to a collection of their names, and then collect them into a list. The resulting list contains the names of all ships with more than 30 cannons, sorted alphabetically.

Combining Intermediate and Terminal Operations

int totalCannonsOfLargeShips = ships.stream()
    .filter(ship -> ship.getCannons() > 40)
    .mapToInt(Ship::getCannons)
    .sum();
System.out.println("Total cannons of large ships: " + totalCannonsOfLargeShips);

This pipeline filters out any ship with fewer than 40 cannons, maps the collection of ships to a collection of their cannons, sums them up, and then prints the total number of cannons of the large ships.

Combining Multiple Pipelines

Predicate<Ship> largeShipPredicate = ship -> ship.getCannons() > 40;
Predicate<Ship> fastShipPredicate = ship -> ship.getSpeed() > 15;
Function<Ship, String> shipNameFunction = Ship::getName;

List<String> namesOfLargeAndFastShips = ships.stream()
    .filter(largeShipPredicate.and(fastShipPredicate))
    .map(shipNameFunction)
    .collect(Collectors.toList());
System.out.println("Names of large and fast ships: " + namesOfLargeAndFastShips);

In this example, we create three separate lambda expressions: one to filter for large ships, one to filter for fast ships, and one to map the collection of ships to a collection of their names. We then combine these three expressions into a single stream pipeline that filters for large and fast ships, maps them to their names, and collects them into a list.

Conclusion

Stream pipelines with lambdas are a powerful tool for working with collections of data in Java. By combining intermediate and terminal operations, as well as multiple pipelines, we can easily transform and reduce our data to find the treasure we seek. Remember to keep your code readable and maintainable by breaking up complex pipelines into smaller, more manageable pieces. Happy treasure hunting, pirates!