简体   繁体   中英

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.

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, eg 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM