I am trying to understand something following the next example:
Stream.of("a1", "a2", "a3")
.map(s -> s.substring(1))
.mapToInt(Integer::parseInt)
.max()
. (etc.)
Why does mapToInt needs the
Integer::parseInt
as a parameter? Shouldn't it be implicit for it? Isn't this parameter redundant?
It's important to differentiate between what calling Stream#mapToInt(ToIntFunction)
does and what the ToIntFunction
argument does.
mapToInt
is what the stream is going to do (i.e. map the elements to an int
).ToIntFunction
argument is how the stream is going to map each element to an int
.Could they have included a no-arg mapToInt
method that implicitly parses String
s to int
s? Yes, but look at how well that works for Stream#sorted()
—and that situation is nowhere near as arbitrary or ambiguous as a no-arg mapToInt
method.
The no-arg sorted
method assumes the elements are Comparable
which is a fundamental, standardized and wide-spread interface in Java—any class can implement the interface (whereas there's only one class that can be a String
). Thus while the method is not type-safe it can be argued the use-case is common enough to justify its existence.
However, a no-arg mapToInt
method that assumes the elements are String
s and parses their content to an int
is a highly specific use-case. Why would such a generic API offer such a specific operation? It'd be an entirely arbitrary decision with no reasonable justification. For instance, why not map each String
to its length
instead? And why is String
being handled specially but not some other type(s)? Hence the method accepts a ToIntFunction
argument that describes how to map the elements to an int
on a case-by-case basis.
It may help to note that Integer::parseInt
is a method reference, which is shorthand for a trivial lambda expression. Both are an implementation of a functional interface which is less verbose option than using an anonymous class. In other words, you're creating a new ToIntFunction
instance and passing it as an argument.
All of the following are functionally equivalent:
// method reference
stream.mapToInt(Integer::parseInt)
// lambda expression
stream.mapToInt((String value) -> Integer.parseInt(value)) // explicit parameter types
stream.mapToInt(value -> Integer.parseInt(value)) // implicit parameter types
// anonymous class
stream.mapToInt(new ToIntFunction<>() {
@Override
public int applyAsInt(String value) {
return Integer.parseInt(value);
}
})
Where stream
is a Stream<String>
.