I am playing around with streams and lambdas in Java 8, as I have never used them before and was trying to convert the ages of everyone who is 20 to 19 and the printing out their names, but I get the following error
Cannot invoke map(Person::getName) on the primitive type void
Here is my code
System.out.println(
people.stream()
.filter(person -> person.getAge() == 20)
.forEach(person -> person.setAge(19))
.map(Person::getName));
If someone could tell me why this is happening or let me know how to improve or amend this code, it would be greatly appreciated.
forEach
is a terminal operation and will not return the stream it works on.
In general, you should avoid using forEach
to modify the stream, even as a final operation. Instead, you should use map
to modify the items of your stream.
Here is an example of how to do it, which includes a legitimate use of forEach
:
people.stream()
.filter(person -> person.getAge() == 20)
.map(person -> new Person(person.getName(), person.getAge() -1 /*, ...*/))
.forEach(System.out::println);
A few notes on that code :
map(...)
only transforms the stream, not the datasource (the people
Collection
). If you want to use the transformed result later, you will want to .collect()
the Stream
into a new Collection
with an appropriate Collector
(e.g. Collectors.toList()
)
if you follow that road, your new terminal operation is collect()
, and you can't use .forEach()
anymore. One solution would be to use the .forEach()
method of the new Collection
(no need for a stream
, forEach()
is implemented on Iterable
since 1.8), another would be to use .peek()
on the stream where you map()
as a non-terminal equivalent to .forEach()
About the use of peek()
, note that the non-terminal operations are driven by the terminal operation : if this one only returns a single element of the stream (like .findFirst()
), non-terminal operations will only be executed for this element, and you shouldn't expect otherwise.
You can write multiple statements inside of forEach :
public class Person
{
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return name + "(" + age + ")";
}
}
public class LambdaPeople
{
public static void main(String[] args) {
Person andy = new Person("Andy", 19);
Person bob = new Person("Bob", 21);
Person caroline = new Person("Caroline", 20);
List<Person> people = new ArrayList<>();
people.add(caroline);
people.add(andy);
people.add(bob);
people.stream()
.filter(person -> person.getAge() == 20)
.forEach(person -> {
person.setAge(19);
System.out.println(person);
});
}
}
It returns :
Caroline(19)
The two next methods are for documentation purpose. peek and map are intermediate operations. Without forEach at the end, they wouldn't be executed at all.
If you want to use map :
people.stream()
.filter(person -> person.getAge() == 20)
.map(person -> {
person.setAge(19);
return person;
})
.forEach(System.out::println);
If you want to use peek :
people.stream()
.filter(person -> person.getAge() == 20)
.peek(person -> person.setAge(19))
.forEach(System.out::println);
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