Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do java8 Stream's filter() and map() method use iterations?

I have a POJO in Person.java file:

public class Person {
    private String name;
    private int age;

    public Person(String n, int a) {
        name = n;
        age = a;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public boolean isAdult() {
        return getAge() >= 18;    
    }
}

And then I have a Demo.java file which creates a list of persons and uses streams to filter and print content from the list:

import java.util.*;

public class Demo {
    public static void main(String[] args) {
        List<Person> people = createPeople();
        List<String> names = people.stream()
                                   .filter(person -> person.isAdult())
                                   .map(person -> person.getName())
                                   .collect(toList());
        System.out.println(names);
    }

    private static List<Person> createPeople() {
        List<Person> people = new ArrayList<>();
        people.add("John", 19);
        people.add("Joe", 21);
        people.add("Jill", 16);
        people.add("Sylvester", 18);
        people.add("Hillary", 17);
        people.add("Donald", 4);

        return people;
    }
}  

I wanted to know:

1> Does filter() and map() internally use a loop to iterate over all the Person objects in the List people?

2> If yes, do they loop over all the objects in the list two different times (1st iteration by filter() and other by map())?

3> If yes again, if I add another map() or filter() method, will it loop over all the objects again for the third time?

4> If yes again, then how is it different performance wise from our traditional imperative style coding (in-fact, in traditional imperative style, we could do all the filtering and mapping in 1 single loop most of the times. So performance wise, imperative style coding would perform better than streams in such a case.)?

PS: If there is a No to any of the above questions, please add an explanation regarding how things work then.

One more: Is there a difference in iteration done by the stream internally and the iteration we do in imperative style? Please shed some more light to my knowledge with some detailed explaination.

like image 868
Aditya Singh Avatar asked Jan 14 '17 20:01

Aditya Singh


People also ask

What is difference between map and filter in java8?

Filter takes a predicate as an argument so basically you are validating your input/collection against a condition, whereas a map allows you to define or use a existing function on the stream eg you can apply String. toUpperCase(...) etc. and transform your inputlist accordingly.

Can we use filter and map together in Java 8?

Once we have the Stream of Integer, we can apply maths to find out even numbers. We passed that condition to the filter method. If we needed to filter on String, like select all string which has length > 2 , then we would have called filter before map. That's all about how to use map and filter in Java 8.

Which is the new method introduced in Java 8 to iterate over a collection?

Introduced in Java 8, the forEach loop provides programmers with a new, concise and interesting way to iterate over a collection.

Can we use map and filter together in Java?

All you need is a mapping function to convert one object to another, and the map() function will do the transformation for you. It is also an intermediate stream operation which means you can call other Stream methods like a filter or collect on this to create a chain of transformation.


1 Answers

First, your code doesn't compile, because map() returns a Stream, not a List. You must end the stream chain with a terminal operation, and both filter() and map() are intermediate operations. Says so right there in the javadoc. In your case, you need to add .collect(Collectors.toList()) to make it compile and run ok.

1> Does filter() and map() internally use a loop to iterate over all the Person objects in the List people?

No. The terminal operation is doing the looping.

Since questions 2 to 4 assumes a Yes answer, they have no answer.

If there is a No to any of the above questions, please add an explanation regarding how things work then.

Read the documentation, or search the web. It's pretty well explained.

Is there a difference in iteration done by the stream internally and the iteration we do in imperative style?

Yes, e.g. streams can utilize parallel thread execution. Even single-threaded there is a difference in how the entire operation works, though logically, at a high level, they are essentially the same.


Example

In your code, with the collect() call added, the equivalent imperative code would be:

List<String> names = new ArrayList<>();
for (Person person : people)
    if (person.isAdult())
        names.add(person.getName());

To compare to what the stream logic does, first you define the lambdas passed to filter() and map():

Predicate<Person>        filter = person -> person.isAdult();
Function<Person, String> map    = person -> person.getName();

Then you get the Collector by calling Collectors.toList(), and retrieve the objects it provide:

Collector<String, List<String>, List<String>> collector = (Collector) Collectors.toList();
Supplier<List<String>>               supplier    = collector.supplier();
BiConsumer<List<String>, String>     accumulator = collector.accumulator();
Function<List<String>, List<String>> finisher    = collector.finisher();

Now, the stream() call basically provides an Iterator (it's actually a Spliterator) and the collect call will iterate, so combined they are equivalent to the for loop. I won't cover the full logic of how Stream, Spliterator and collect() works. Search the web if you need more detail on that.

So, the imperative for loop above becomes:

List<String> list = supplier.get();        // list = new ArrayList<>()
for (Person person : people)               // collect() loop using Spliterator from stream()
    if (filter.test(person)) {             // if (person.isAdult())
        String value = map.apply(person);  // value = person.getName()
        accumulator.accept(list, value);   // list.add(value)
    }
List<String> names = finisher.apply(list); // names = list
like image 116
Andreas Avatar answered Oct 07 '22 02:10

Andreas