Practical examples of using the Stream API

Hi all! I decided to write an article with practical examples of using the Stream API. There will be no theory in this article – only hardcore and practical examples. Go!

As a “guinea pig” I will use a project that you can find here.

For testing I will use Postman.

The project has three entities with which we will work: Client, Product and Booking. The connections between them can be seen in the following picture.

The Product entity has an enum Category. The central entity is Booking, which includes a Client, a List, and an enum Status. That is, Booking is an order of a list of products that a client makes.

I will use the H2 database in the project. To view the data, after starting the project, follow the link http://localhost:8080/h2-console enter username: user and password: password there and you will see this:

Access to this thing is due to spring.h2.console.enabled=true, which I wrote in application.properties, here you can also change the login and password for accessing the database.

Hibernate will generate the tables for us at the expense of spring.jpa.hibernate.ddl-auto=create, which is registered in application.properties, as it is written create – every time the project starts, the tables in the database will be deleted and recreated. This can be seen in the logs as spring.jpa.show-sql=true and spring.jpa.properties.hibernate.format_sql=true are written.

To generate data in the database tables, I wrote the following class

@Component
public class DataLoader {
    private final long NUMBER_BOOKINGS = 30;

    @Bean
    public CommandLineRunner loadDataClient(ClientRepository clientRepository, ProductRepository productRepository, BookingRepository bookingRepository) {
        return (args) -> {
            Faker faker = new Faker();
            long indexForProduct = 1;
            for (long i = 0; i <= NUMBER_BOOKINGS; i++) {
                Set<Product>products = new HashSet<>();
                long indexToIncreaseProduct =0;
                for (long j = indexForProduct; j <= faker.number().numberBetween(indexForProduct, indexForProduct+10); j++) {
                    indexToIncreaseProduct = j;
                        Product product = productRepository.save(new Product(j, faker.lorem().word(),
                                Category.valueOf(Category.values()[faker.number().numberBetween(0, 5)].name()),
                                faker.number().randomDouble(2, 1, 100)));
                        products.add(product);
                }
                indexForProduct += indexToIncreaseProduct;

                Client client = clientRepository.save(new Client(i, faker.name().firstName() + " " + faker.name().lastName()));

                bookingRepository.save(new Booking(i, LocalDate.of(2022, Month.JANUARY, 1).plusDays(faker.number().numberBetween(1, 365)), Status.valueOf(Status.values()[faker.number().numberBetween(0, 3)].name()),
                        client, products));
            }


        };
    }
}

This code generates 30 orders, customers, and a random number of products per order and stores it all in a database. You can change the number of orders at your discretion. To generate data, I used a third-party Faker library, who are interested can read about it here https://www.baeldung.com/java-faker.

With the introductory part, you’re all set to get started with the Stream API.


Example 1. Find all products in the “Book” category, with a price greater than 50

@Override
    public List<Product> findAllProductsBelongsCategoryBookWithPriceMore50() {
        return productRepository.findAll()
                .stream()
                .filter(product -> product.getCategory().equals(Category.BOOK))
                .filter(product -> product.getPrice() > 50)
                .collect(Collectors.toList());
    }

We find all the products in the database, then we apply two filters, the first leaves us only the products of the “Book” category, and the second compares the price with the condition and leaves only the products where the price is greater than 50. At the end, we do a terminal operation and collect our stream into a list of products , but only with those that passed all the filters.

To test this code, you can send a Postman request to http://localhost:8080/api/v1/product/findAllProductsBelongsCategoryBookWithPriceMore50

and get

or something similar, since the initial data will be generated differently for everyone. Also, to test all the examples, there are two controllers (BookingController and ProductController) with written endpoints, by calling which you can test the examples.

Example 2: Find all products ordered in a specific, given time period

@Override
    public List<Product> findAllProductOrderedBetweenDates(LocalDate start, LocalDate finish) {
        return bookingRepository.findAll()
                .stream()
                .filter(booking -> booking.getOrderDate().compareTo(start) >= 0)
                .filter(booking -> booking.getOrderDate().compareTo(finish) <= 0)
                .flatMap(booking -> booking.getProducts().stream())
                .collect(Collectors.toList());
    }

We find all orders, after that we apply two filters that filter our orders so that only those ordered in a given period go further. We then apply flatMap() which makes one new stream from all the products in the orders. The last one is a terminal operation that combines all products into one list.

To test this example, we need in Postman to also pass the dates of the period for which we want to make a selection.

Example 3. Find the cheapest product in the category “Medicine”

 @Override
    public Optional<Product> findCheapestProductInCategoryMedicine() {
        return productRepository.findAll()
                .stream()
                .filter(product -> product.getCategory().equals(Category.MEDICINE))
                .min(Comparator.comparing(Product::getPrice));
    }

We get a stream with all products, then we apply a filter that leaves only the products of the “Medicine” category and then using the min() operation we find the cheapest product.

Example 4: Find all products ordered on a specific day

    @Override
    public List<Product> findAllProductOrderedInDate(LocalDate date) {
        return bookingRepository.findAll()
                .stream()
                .filter(booking -> booking.getOrderDate().isEqual(date))
                .flatMap(booking -> booking.getProducts().stream())
                .collect(Collectors.toList());
    }

We find all orders, then we apply a filter where we compare the date of our order with a given date. We then apply flatMap() which makes one new stream from all the products in the orders. The last one is a terminal operation that combines all products into one list.

Example 5. Get statistics for products in the category “Food”

    @Override
    public DoubleSummaryStatistics obtainCollectionOfStaticAllProductsCategoryFood() {
        return productRepository.findAll()
                .stream()
                .filter(product -> product.getCategory().equals(Category.FOOD))
                .mapToDouble(Product::getPrice)
                .summaryStatistics();

    }

We get a stream with all products, then we apply a filter that leaves only the products of the category “ Food ”, and then using the mapToDouble() operation we form one data stream with the prices of all products of this category, and then using the summaryStatistics() terminal operation we get statistical data.

By sending a request to Postman http://localhost:8080/api/v1/product/obtainCollectionOfStaticAllProductsCategoryFood we will get statistical data:

your specific values ​​will be different.

Example 6: Group products by category

@Override
    public Map<String, List<String>> getMapWithListProductsNameByCategory() {
        return productRepository.findAll()
                .stream()
                .collect(Collectors.groupingBy(
                        product -> product.getCategory().name(),
                        Collectors.mapping(Product::getName, Collectors.toList())
                ));
    }

Initially, we get a list of all products and make a stream out of it. Then, using the groupingBy() operation, we specify by what we want to group products (by category name) and, using the mapping() operation, we specify what data should be collected in the list and belong to this category.

Example 7: Get the most expensive products by category

 @Override
    public Map<String, Optional<Product>> getMostExpensiveProductByCategory() {
        return productRepository.findAll()
                .stream()
                .collect(
                        Collectors.groupingBy(
                        product -> product.getCategory().name(),
                                Collectors.maxBy(Comparator.comparing(Product::getPrice)))
                );
    }

Initially, we get a list of all products and make a stream out of it. Then, using the groupingBy() operation, we specify by what we want to group products (by category name) and using the maxBy() operation, we specify which product (in our case, the most expensive one) should belong to this category.

Example 8. Get all orders that belong to the category “Sport”

@Override
    public List<Booking> findAllBookingWithProductsBelongCategorySport() {
        return bookingRepository.findAll()
                .stream()
                .filter(booking -> booking.getProducts().stream().anyMatch(product -> product.getCategory().equals(Category.SPORT)))
                .collect(Collectors.toList());
    }

We find all orders and make a stream out of it. After that, we apply a filter in which we get a list of products from each order and make a stream out of it, and using the anyMatch() operation, we check if there is at least one product with the “Sport” category in this list. After that, we collect everything into one list using the collect() terminal operation.

Example 9. Get the last three orders

@Override
    public List<Booking> findThreeMostRecentBooking() {
        return bookingRepository.findAll()
                .stream()
                .sorted(Comparator.comparing(Booking::getOrderDate).reversed())
                .limit(3)
                .collect(Collectors.toList());
    }

We get a list of orders and make a stream out of it, then we sort this stream using the sorted() operation, where we pass the order date (Booking::getOrderDate) as the order by which we sort, and since the list will be sorted from the oldest order to the most the latter, you need to use the reserved() operation to expand the list in the opposite direction – from the newest order to the oldest. The limit() operation leaves only the required number of elements for us, and the last terminal operation collects everything into a list.

Example 10. Calculate the total amount of all orders for a certain period

 @Override
    public Double calculateTotalSumAllBookingsBetweenDates(LocalDate start,
                                                           LocalDate finish) {
        return bookingRepository.findAll()
                .stream()
                .filter(booking -> booking.getOrderDate().compareTo(start) >= 0)
                .filter(booking -> booking.getOrderDate().compareTo(finish) < 0)
                .flatMap(booking -> booking.getProducts().stream())
                .mapToDouble(Product::getPrice)
                .sum();
    }

We get a list of orders, after that we apply two filters, with the help of which we leave only orders from the time range we need. Then, using the flatMap() operation, we make a common flow of all products and apply the mapToDouble() operation, which makes a price flow from the common product flow. The final terminal operation sum() sums all the numbers.

Example 11: Find the average value of the cost of all orders with the status APPROVED for a certain date

 @Override
    public Double calculateAverageAllBookingsWithStatusApprovedOnDate(LocalDate start) {
        return bookingRepository.findAll()
                .stream()
                .filter(booking -> booking.getOrderDate().isEqual(start))
                .filter(booking -> booking.getStatus().equals(Status.APPROVED))
                .flatMap(booking -> booking.getProducts().stream())
                .mapToDouble(Product::getPrice)
                .average()
                .orElse(0);
    }

We find all orders, then we apply two filters. The first leaves only orders for a certain date, and the second selects only orders with the APPROVED status. Then, using the flatMap() operation, we get the lists of products from all orders and make a common stream out of them, and using the mapToDouble() operation, we get the stream of their prices from the general product stream. The final terminal operation average() finds the average of these prices.

Example 12. Get a Map<>, where the key is the order ID and the value is the number of products in this order

@Override
    public Map<Long, Integer> getMapWithBookingIdAndCountProduct() {
        return bookingRepository.findAll()
                .stream()
                .collect(Collectors.toMap(Booking::getId, booking -> booking.getProducts().size()));
    }

We get a list of all orders and make a stream out of it. Then we use the collect() operation, where we indicate that we want to collect everything in a Map<> and indicate that the key will be the order Id, and the value will be the number of products in this order.

Example 13. Get a Map<> where the key is the customer and the value is the list of orders that the customer has made

    @Override
    public Map<Client, List<Booking>> getMapWithClientAndListBookings() {
        return bookingRepository.findAll()
                .stream()
                .collect(Collectors.groupingBy(Booking::getClient));
    }

We get a list of all orders and make a stream out of it. After we apply the collect () operation, where we indicate that we want to group by clients and that’s it. How much easier the code is when using the stream API.

Example 14. Get a Map<> where the key is an order and the value is the total cost of all products in it

    @Override
    public Map<Booking, Double> getMapWithBookingAndProductTotalSum() {
        return bookingRepository.findAll()
                .stream()
                .collect(Collectors.toMap(
                        Function.identity(),
                        booking -> booking.getProducts().stream()
                                .mapToDouble(Product::getPrice).sum()
                ));
    }

We get a list of all orders and make a stream out of it. Then we use the collect() operation, where we indicate that we want to collect everything in a Map<> and indicate that the key will be the order (Function.identity()), and the value – we get lists of products from all orders and make them a common stream and using the mapToDouble() operation, the cases from the general stream of products stream their prices. The final terminal operation sum() finds the sum of the given prices.

Thanks to everyone who read to the end.

Good luck to everyone in learning the stream API.

Similar Posts

Leave a Reply