How to Filter Collections in Java 8 with Streams and Predicates

Java 8 provides excellent features to support filtering of elements in Java Collections. Prior to Java 8, only better way to filter elements is by using foreach loop or iterating over Collection using Iterator and selecting required object, leaving out rest. Though that approach work, it was very difficult to run them in parallel and take advantage of multiple CPU available in modern day servers. Java 8 provides Streams, which not only makes it easy to run any operation parallel but also support lazy loading and lazy evaluation, which means as soon as filtering condition is satisfied, it stooped doing work, doesn't matter how many object collection contains. You can filter Java Collections like List, Set or Map in Java 8 by using filter() method of Stream class. You first need to obtain stream from Collection by calling stream() method and than you can use filter() method, which takes a Predicate as only argument. Predicate is a functional interface with couple of boolean valued method e.g. test(), which returns boolean true or false. Java 8 uses this method to filter Collection. Just remember that filter doesn't remove elements which matches the condition given in predicate, instead it selects them in output stream. I agree, little bit counter intuitive, select would have been better name for this method, but once you used it couple of times, you will be Ok. For example, if you have list of String and you want another list which contains only long string say whose length is greater than 20 character, you can use filter method to do that and you don't need a loop. Java 8 Stream is very efficient replacement of looping both design and performance wise, because it separates What to do from how to do, just like SQL. Leaving the implementation part to platform.



Java 8 Filter Method

As I said filter() method is defined in Stream class, it takes a Predicate object and returns a stream consisting of the elements from original stream which matches the given Predicate. It is an intermediate operation, which means you can call any other method of Stream e.g. count() as stream will not be closed due to filtering. Predicate is applied to each element of Collection to check whether a particular element should be included in filtered stream or not. Predicate should be stateless and non interfering so that if needed filter operation can run in parallel using parallel stream. In Java8, Predicate is a functional interface with couple of boolean valued method used to test input against the condition. In our case test(T t) method is used, which evaluate this predicate in given argument. Since filter() method accept a functional interface, you can also pass lambda expression to it, which is what we will do. You can pass any condition to filter elements e.g. either by using relational operator e.g. less than, greater than or equal to or by using methods like equals() and equalsIgnoreCase() as shown in our sample program. 



Java 8 Example of Filtering List using Stream

This is our sample program to demonstrate power of Java 8 Stream and Filter method. By using these Java 8 enhancements you can perform SQL like operation in Java e.g.

SELECT * FROM Deals WHERE type = 'ELECTRONIC'

can be written using Java 8 stream and filter method as

 deals.stream()
        .filter(deal -> deal.type() == Deal.Type.ELECTRONIC)
        .forEach(System.out::println)

In this example, we are passing a lambda expression to filter method, which returns a boolean. It's actually anonymous function test(), we have omitted type information for deal variable because it will be inferred by JVM easily, making our code concise. In short, you can pass a lambda expression to filter method until result is boolean. The forEach() method is a terminal operation and used here to print all deals present in filtered collection.

Now let's see second example of filtering List in Java. Now we need all deals which are expiring in March. In SQL we can write query like this :

SELECT * FROM Deals WHERE validity='MARCH'

and in Java 8, we can write following code :

deals.stream()
         .filter(deal -> deal.validity().getMonth() == Month.MARCH)
         .forEach(System.out::println);

Our third example is about filtering elements based upon greater than condition. How about getting all deals which with 30% or more discounts? We can write following query in SQL :

SELECT * FROM Deals WHERE discountPercentage >= 30;

In Java 8 you can use filter method  like below to do the same :

deals.stream()
        .filter(deal -> deal.discount().compareTo(new BigDecimal("00.30")) > 0)
        .forEach(System.out::println);


Let's see one more example of filtering collection in Java. How about finding all deals from Apple, don't you know iPad and iPhone? We can write SQL query like following to get those deals :

SELECT * FROM Deals WHERE provider='Apple';

In Java 8, You can do  :

deals.stream()
        .filter(deal -> deal.provider().equalsIgnoreCase("Apple"))
        .forEach(System.out::println);

Here is the complete code which you can run in your favorite IDE or command prompt if you are a down to earth programmer.


How to use Filter method in Java 8

package test;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.List;

/**
 * Simple Java value class to represent a deal
 */ 
public class Deal {

    public enum Type {
        BOOK,
        ELECTRONIC,
        TRAVEL,
        COSMATIC,
        ACTIVITY,
    }
    private final String provider;
    private final Type type;
    private final BigDecimal price;
    private final BigDecimal discount;
    private final String title;
    private final LocalDate validity;

    public Deal(String provider, Type type, BigDecimal price,
            BigDecimal discount, String title, LocalDate validity) {
        this.provider = provider;
        this.type = type;
        this.price = price;
        this.discount = discount;
        this.title = title;
        this.validity = validity;
    }

    public String provider() {
        return provider;
    }

    public Type type() {
        return type;
    }

    public BigDecimal price() {
        return price;
    }

    public BigDecimal discount() {
        return discount;
    }

    public String title() {
        return title;
    }

    public LocalDate validity() {
        return validity;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(title).append(" from ").append(provider).
                append(", price : ").append(price).
                append(", offer valid till ").append(validity).
                append(" category : ").append(type);
        return sb.toString();
    }

}


/**
 * Java 8 Example to filter Collection on Predicates using Stream API. this
 * program also uses new Date and Time API, lambdas, method reference etc.
 * 
* @author Javin Paul
 */
public class Java8FilterDemo {

    public static void main(String args[]) {

        List deals = loadDeals();
        System.out.println("All Deals");
        System.out.println("--------------------------------");

        // this will print all deals from list
        deals.forEach(System.out::println);

        System.out.println("--------------------------------");

        // Filtering elements from a Collection in Java 8
        // filtering on category
        System.out.println("All deals for Electornic items");
        deals.stream().filter(deal -> deal.type() == Deal.Type.ELECTRONIC).forEach(System.out::println);
        System.out.println("--------------------------------");

        // filter all deals which are expiring on March
        System.out.println("Deals expiring on March");
        deals.stream().filter(deal -> deal.validity().getMonth() == Month.MARCH).forEach(System.out::println);
        System.out.println("--------------------------------");

        // filter all deals which has more than 30% discount
        System.out.println("All deals with 30% or more discount");
        deals.stream().filter(deal -> deal.discount().compareTo(new BigDecimal("00.30")) > 0).forEach(System.out::println);
        System.out.println("--------------------------------");

        // filter all deals from companies
        System.out.println("All deals from Apple");
        deals.stream().filter(deal -> deal.provider().equalsIgnoreCase("Apple")).forEach(System.out::println);
        System.out.println("--------------------------------");
    }

    private static List loadDeals() {
        List deals = new ArrayList<>();
        deals.add(new Deal("Manning", Deal.Type.BOOK,
                new BigDecimal("30.00"), new BigDecimal(".50"),
                "Save 50% on Java 8 Books", LocalDate.of(2014, Month.MARCH, 20)));

        deals.add(new Deal("Amazon", Deal.Type.BOOK,
                new BigDecimal("20.00"), new BigDecimal(".20"),
                "Save 20% on Clean Code", LocalDate.of(2014, Month.FEBRUARY, 10)));

        deals.add(new Deal("Kathy Pacific", Deal.Type.TRAVEL,
                new BigDecimal("300.00"), new BigDecimal(".40"),
                "Save 40% on flight to USA", LocalDate.of(2014, Month.FEBRUARY, 19)));

        deals.add(new Deal("Luftanse", Deal.Type.TRAVEL,
                new BigDecimal("30.00"), new BigDecimal(".50"),
                "Save 50% on flight to Berlin", LocalDate.of(2014, Month.MARCH, 27)));

        deals.add(new Deal("Trekking", Deal.Type.ACTIVITY,
                new BigDecimal("400.00"), new BigDecimal(".50"),
                "Save 50% on Trekking", LocalDate.of(2014, Month.MARCH, 25)));

        deals.add(new Deal("Apple", Deal.Type.ELECTRONIC,
                new BigDecimal("800.00"), new BigDecimal(".10"),
                "10% discount on iPhone 5S", LocalDate.of(2014, Month.APRIL, 19)));

        deals.add(new Deal("Samsung", Deal.Type.ELECTRONIC,
                new BigDecimal("700.00"), new BigDecimal(".20"),
                "20% discount on Galaxy S4", LocalDate.of(2014, Month.MARCH, 18)));

        deals.add(new Deal("LG", Deal.Type.ELECTRONIC,
                new BigDecimal("390.00"), new BigDecimal(".50"),
                "Save 40% on LG Smartphones", LocalDate.of(2014, Month.FEBRUARY, 17)));

        deals.add(new Deal("Sony", Deal.Type.ELECTRONIC,
                new BigDecimal("500.00"), new BigDecimal(".50"),
                "Save 50% on Sony Viao Laptops", LocalDate.of(2014, Month.APRIL, 10)));
        return deals;
    }

}


Output:

All Deals
--------------------------------
Save 50% on Java 8 Books from Manning, price : 30.00, offer valid till 2014-03-20 category : BOOK
Save 20% on Clean Code from Amazon, price : 20.00, offer valid till 2014-02-10 category : BOOK
Save 40% on flight to USA from Kathy Pacific, price : 300.00, offer valid till 2014-02-19 category : TRAVEL
Save 50% on flight to Berlin from Luftanse, price : 30.00, offer valid till 2014-03-27 category : TRAVEL
Save 50% on Trekking from Trekking, price : 400.00, offer valid till 2014-03-25 category : ACTIVITY
10% discount on iPhone 5S from Apple, price : 800.00, offer valid till 2014-04-19 category : ELECTRONIC
20% discount on Galaxy S4 from Samsung, price : 700.00, offer valid till 2014-03-18 category : ELECTRONIC
Save 40% on LG Smartphones from LG, price : 390.00, offer valid till 2014-02-17 category : ELECTRONIC
Save 50% on Sony Viao Laptops from Sony, price : 500.00, offer valid till 2014-04-10 category : ELECTRONIC
--------------------------------
All deals for Electornic items
10% discount on iPhone 5S from Apple, price : 800.00, offer valid till 2014-04-19 category : ELECTRONIC
20% discount on Galaxy S4 from Samsung, price : 700.00, offer valid till 2014-03-18 category : ELECTRONIC
Save 40% on LG Smartphones from LG, price : 390.00, offer valid till 2014-02-17 category : ELECTRONIC
Save 50% on Sony Viao Laptops from Sony, price : 500.00, offer valid till 2014-04-10 category : ELECTRONIC
--------------------------------
Deals expiring on March
Save 50% on Java 8 Books from Manning, price : 30.00, offer valid till 2014-03-20 category : BOOK
Save 50% on flight to Berlin from Luftanse, price : 30.00, offer valid till 2014-03-27 category : TRAVEL
Save 50% on Trekking from Trekking, price : 400.00, offer valid till 2014-03-25 category : ACTIVITY
20% discount on Galaxy S4 from Samsung, price : 700.00, offer valid till 2014-03-18 category : ELECTRONIC
--------------------------------
All deals with 30% or more discount
Save 50% on Java 8 Books from Manning, price : 30.00, offer valid till 2014-03-20 category : BOOK
Save 40% on flight to USA from Kathy Pacific, price : 300.00, offer valid till 2014-02-19 category : TRAVEL
Save 50% on flight to Berlin from Luftanse, price : 30.00, offer valid till 2014-03-27 category : TRAVEL
Save 50% on Trekking from Trekking, price : 400.00, offer valid till 2014-03-25 category : ACTIVITY
Save 40% on LG Smartphones from LG, price : 390.00, offer valid till 2014-02-17 category : ELECTRONIC
Save 50% on Sony Viao Laptops from Sony, price : 500.00, offer valid till 2014-04-10 category : ELECTRONIC
--------------------------------
All deals from Apple
10% discount on iPhone 5S from Apple, price : 800.00, offer valid till 2014-04-19 category : ELECTRONIC

That's all about how to filter collection in Java 8.  This is just an example of  super power of new stream API. You can write more expressive code, which is easy to understand, easy to optimize and super easy to run in parallel without you worrying about multi-threading nightmare. Together with lambda expression, method references and new Stream API, Java has become super expressive language without added boiler plate.

 If you like this Java 8 tutorial and hungry for more Java 8 articles, don't forget to checkout these :
  • How to use Lambda Expression in Place of Anonymous class (read here)
  • How to use Map function in Java 8 (see more)
  • How to use Default method in Java 8. (see here)
  • Java 8 Comparator Example (see example)
  • Free Java 8 tutorials and Books (read book)
  • Top 10 tutorials to Learn Java 8 (read here)


2 comments :

Nguyen Duong Vu said...

Great written!
And I have a question. How can you do with multiple conditions? Thank you.

Javin Paul said...

@Ng, You can chain filter() methods because its not a terminal method like forEach(). just add multiple filter() methods for multiple conditions.

Post a Comment