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.
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.
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.
Introduced in Java 8, the forEach loop provides programmers with a new, concise and interesting way to iterate over a collection.
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.
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()
andmap()
internally use a loop to iterate over all thePerson
objects in theList
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With