Filtering collection in java 8

Almost every java application uses a collection and performs a filtering operation. With java 8 you can throw away those hard to read non portable for loops and take advantage of the Streams API.

Detailed Video Notes

Hey everyone, this is Justin from Level Up Lunch. In episode one we showed how to filter arraylist with guava and today we will we will be introducing the concept of filtering in java 8. Java 8 was released in March of 2014 and this screencast will give you a jump start on some of the enhancements made in the api.

Quick definitions and setup

[0:22]

Before we get started, it is import to understand two key definitions. First, a java predicate is a function that takes in a value and returns true or false. You might pass an object to a Predicate and the predicate will evaluate if it meets the condition returning true or false. Later on we will show a couple of different ways to create a predicate and how to pass the predicate to the Stream.filter method. The second concept or definition is java.util.stream.Stream. The definition of a stream is a sequence of elements supporting sequential and parallel aggregate operations. You can think of Streams as being appended to collections to allow for functional idioms that we are looking to have in java. Streams are not meant to replace collections rather simplify what used to be hard without a third party utility now just easy within the core jdk.

In the spirt of March Madness we created a basketball team object along with a region enum that will represent the regions in the bracket. If you aren't familiar, March Madness is NCAA Men's Division I Basketball Championship tournament played each spring in the United States. The BBTeam class contains 3 different attributes, points scored, team name and region. We have overridden the toString to show pretty output in our examples. We also created an ArrayList of teams initialized and populated in our junit set up method. In the initialization we included a team with a null name so we can demonstrate how to use a predicate to filter this null. This list could be any data that you may obtain from a database, a REST based service where you are pulling back records and there could be bad data and you need to filter it out. Or it could represent where user input validation didn't occur so you might be dealing with bad data.

Creating a predicate

[2:52]

Implementing the predicate interface

There are two primary ways to create a predicate. You can explicitly create a predicate where you implement the java.util.function.Predicate interface. Doing this below we will create a predicate named the westRegion where we pass in a BBTeam class checking if the BBTeam.region == Region.WEST. Ultimately this predicate will ask, "Does this Basketball team belong in the west region" and in combination with Stream.filter we will obtain all teams that are in the west region.

Predicate<BBTeam> westRegion = new Predicate<BBTeam>() {
    @Override
    public boolean test(BBTeam t) {
        return t.region == Region.WEST;
    }
};

Using a lambda expression

The second way to create a predicate is through a lambda expression which we won't go into great detail. Just note that we will explicitly specify the type which is represented by 'p' and filter any BBTeams where p.region == Region.EAST. Using a lambda expression is a slimmer trim way or a short cut to creating a java predicate.

Predicate<BBTeam> eastRegion = (BBTeam p) -> p.region == Region.EAST;

Filtering with defined predicate

Diving in, we take the teams arraylist and apply a filter which will trim down the list based on the predicate we will pass. In order to do this, we will convert the the list to a stream and call the filter method passing in the westRegion predicate. To display the results we call the Stream.forEach passing in a lambda expression sending the results to the PrintStream. At a high level, this statement will iterate over every single item and ask if the BBTeam belongs to the west region.

@Test
public void filter_by_region() {

    teams.stream().filter(westRegion).forEach(p -> System.out.println(p));

}

Filtering with lambda expression

[5:42]

Next, lets look at how we can filter by score. You might have a score program that displays "Who has the highest score" and a possible ratings. To follow the same behavior we will copy the same expression as above teams.stream().filter. Instead of passing a predefined predicate, we will use a lambda expression to create the predicate that will return all the teams with a score greater than 60. This will demonstrate how to filter a list of objects with a lambda expression.

@Test
public void filter_by_score() {

    teams.stream().filter(p -> p.pointScored >= 60)
            .forEach(p -> System.out.println(p));
}

Composing multiple predicates

[8:55]

Next we will filter by team converting the stream to a collection. To set up this example, we will add a null reference to the teams list. Again, we will use a lambda expression to create a predicate where the team name equals Wisconsin. Running this will result in a NullPointerException when we try to apply the bbTeam.equals because you can't call equals on a null object or a null name.

teams.stream().filter(p -> p.teamName.equals("Wisconsin")).forEach(p -> System.out.println(p));

To get around the NullPointerException we will compose a series of Predicates to filter the results before we execute p.teamName.equals. Thinking through this we want to filter any null reference BBTeams and any BBTeams name equaling null from the teams collection. Creating the first predicate nonNullPredicate we again will use a lambda expression Objects::nonNull. Objects is class contains static utility methods for operating on objects which was introduced in java 7. The second nameNotNull predicate will return true if the BBTeam.name is not null. Calling the Predicate.and we will combine all three predicates (nonNullPredicate, nameNotNull and teamWIPredicate) passing the composed predicate to the filter method.

The last thing we will do is call Stream.collect which will take all the elements in the Stream and return the object of the type specified. We will do this by using Collectors.toList which will accumulate the input elements into a new ArrayList.

@Test
public void filter_by_team_to_collection() {

    Predicate<BBTeam> nonNullPredicate = Objects::nonNull;
    Predicate<BBTeam> nameNotNull = p -> p.teamName != null;
    Predicate<BBTeam> teamWIPredicate = p -> p.teamName.equals("Wisconsin");

    Predicate<BBTeam> fullPredicate = nonNullPredicate.and(nameNotNull)
            .and(teamWIPredicate);

    List<BBTeam> teams2 = teams.stream().filter(fullPredicate)
            .collect(Collectors.toList());

    System.out.println(teams2);
}

Thanks for joining us today and I hope this provided an intro to filtering a stream/collection using java 8. Have a a great day! Thanks!