Java 8 Streams, working out the basics

Java 8 came with lots of interesting new stuff, and I want to highlight the Streams framework among them.

Streams framework classes support functional-style operations on streams of elements, allowing a cleaner programming style and also the capability of “chaining” operations as processing pipelines. Streams allows us perform operations such as map-reduce transformations on collections, such as : Max, Min, Sum and son on.

Stream operations allow us to express sophisticated data processing queries over collections. Collections is one of the most used features on the Java language, as it gives us the ability to group and process data. As an example, we can create a collection of candidates with name and score, then discover which one of them got the highest score in order to hire him.

Collections processing hasn’t been simple in previous Java versions, as it is boring, repetitive and extremely error prone because every time we need to repeat the same ( or almost the same ) loop statement, check using many if/else/case statements, and so on. Wouldn’t it be nice if we had an interface that took all of that boilerplate from our developers hands and allows us to express what we want in a more SQL-like operation ? simply saying something like : PLEASE, GIVE ME THE CANDIDATE WHICH HAS THE HIGHEST SCORE OF ALL !

Great news, now we have a mechanism inside the Java 8 language that gives us just that !

Introducing Streams

Let’s say we have a candidates list to process. First thing we’ll do is to create a class which will represent a candidate :

package au.com.riosoftware.functional.domain;

public class Candidate {

    private String name;
    private int score;

    public Candidate(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Candidate{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}

Then we’re going to define another class to play with our Candidate class, and create a list of candidates with their score :

public class Application {

    public static void main(String[] args) {

        List<Candidate> candidates = Arrays.asList(
                new Candidate("Marcio", 50),
                new Candidate("Ana", 94),
                new Candidate("Carl", 22),
                new Candidate("Julia", 63),
                new Candidate("David", 78),
                new Candidate("Ash", 38),
                new Candidate("Brett", 40),
                new Candidate("Suzi", 2),
                new Candidate("Kent", 88)
                );
}

Ok, let’s start slowly. Right now I feel I need to print all candidates from the candidates list.

Before Java 8, we would do :

for (Candidate c : candidates) {
    System.out.println(c);
}

Produces :

Candidate{name='Marcio', score=50}
Candidate{name='Ana', score=94}
Candidate{name='Carl', score=22}
Candidate{name='Julia', score=63}
Candidate{name='David', score=78}
Candidate{name='Ash', score=38}
Candidate{name='Brett', score=40}
Candidate{name='Suzi', score=2}
Candidate{name='Kent', score=88}

We can achieve the same result using Java 8 stream() method from our list :

candidates.stream().forEach(System.out::println);

Ok, I admit. So far, nothing fancy happening here 🙂

We’re simply printing our all candidates from our list using a stream from our List<Candidate>, but as you can see I don’t have to iterate over it myself, the framework will do it as I can invoke the forEach method on the result Stream and pass a function to be executed against every object from the target collection. The method stream() in fact comes from :

Collections.java

public interface Collection<E> extends Iterable<E> {

   // Lots of code happening here, which I stripped out.

    /**
     * Returns a sequential {@code Stream} with this collection as its source.
     *
     * <p>This method should be overridden when the {@link #spliterator()}
     * method cannot return a spliterator that is {@code IMMUTABLE},
     * {@code CONCURRENT}, or <em>late-binding</em>. (See {@link #spliterator()}
     * for details.)
     *
     * @implSpec
     * The default implementation creates a sequential {@code Stream} from the
     * collection's {@code Spliterator}.
     *
     * @return a sequential {@code Stream} over the elements in this collection
     * @since 1.8
     */
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

   // Lots of code happening here, which I stripped out.
}

Now that we’re more familiar with Streams we go to something more advanced.

Let’s extract all candidate names to another list :

Pre-Java 8

List<String> names = new ArrayList<>();
for (Candidate c : candidates) {
    names.add(c.getName());
}

Java 8

List<String> names = candidates.stream().map(c -> c.getName()).collect(toList());

Produces :

[Marcio, Ana, Carl, Julia, David, Ash, Brett, Suzi, Kent]

Ok, from now on I won’t post the Pre-Java 8 codes anymore. Right now I need to get the same candidates names list, but this time I need it to be ordered.

List<String> orderedCandidateNames = candidates.stream().map(c -> c.getName()).sorted().collect(toList());

Now, let’s get to a bit more advanced collections processing with filter and a more functional approach.

My next task is to filter all candidates with score above 60 points, which pre-Java8 I would need to iterate and test if the value was greater than 60, but now I can simply :

List<Candidate> candidatesOver60Points = candidates.stream().filter(c -> c.getScore() > 60).collect(toList());

Produces :

[Candidate{name='Ana', score=94}, Candidate{name='Julia', score=63}, Candidate{name='David', score=78}, Candidate{name='Kent', score=88}]

Just got another requirement which says I have to filter all candidates over 60 points and get them ascending sorted by score. I can use a functional approach of passing a block of code to my sorted method, something similar I did for filter :

List<Candidate> orderedCandidatesOver60Points = candidates.stream().filter(c -> c.getScore() > 60).sorted((candidate1, candidate2) -> candidate1.getScore() - candidate2.getScore()).collect(toList());

As I said, I’m passing a function to the sorted method, which has 2 candidate objects, and the comparison is done by their scores. Another useful approach is to define a comparator for this king of job, so we can have a more expressive code. I will create two comparators, one comparator for ascending and the other for descending sort :

Comparator<Candidate> compareAscendingScore = (candidate1, candidate2) -> candidate1.getScore() - candidate2.getScore();

Comparator<Candidate> compareDescendingScore = compareAscendingScore.reversed();

I can easily integrate into my code :

List<Candidate> candidatesOver60PointsAscendingByPoints = candidates.stream().filter(c -> c.getScore() > 60).sorted(compareAscendingScore).collect(toList());

List<Candidate> candidatesOver60PointsDescendingByPoints = candidates.stream().filter(c -> c.getScore() > 60).sorted(compareDescendingScore).collect(toList());

Produces :

[Candidate{name='Julia', score=63}, Candidate{name='David', score=78}, Candidate{name='Kent', score=88}, Candidate{name='Ana', score=94}]

[Candidate{name='Ana', score=94}, Candidate{name='Kent', score=88}, Candidate{name='David', score=78}, Candidate{name='Julia', score=63}]

 

 

 

5 thoughts on “Java 8 Streams, working out the basics

Leave a Reply

Your email address will not be published. Required fields are marked *