To add an illustration to Giovanni's answer, we can highlight the difference between f::handle
and obj -> f.handle(obj)
if we replace f
with a method call:
static Set<String> f() {
System.out.println(" f() called");
return new HashSet<>();
}
public static void main(String[] args) {
List<String> empty = Collections.emptyList();
List<String> strings = Arrays.asList("foo", "bar");
System.out.println("method reference, no invocations");
empty.forEach(f()::add);
System.out.println("method reference, 2 invocations");
strings.forEach(f()::add);
System.out.println("lambda, no invocations");
empty.forEach(str -> f().add(str));
System.out.println("lambda, 2 invocations");
strings.forEach(str -> f().add(str));
}
Output:
method reference, no invocations
f() called
method reference, 2 invocations
f() called
lambda, no invocations
lambda, 2 invocations
f() called
f() called
So, as you see .forEach(f()::add)
will evaluate f()
right away and then call add(...)
on the result as many times as the lambda is called.
On the other hand, str -> f().add(str)
will not do anything upfront but will call f() every time the lambda is invoked.