@tsolakp's answer shows how to create an inlined lambda of a specific type by casting the lambda expression to the desired functional type.
As to why you can't do:
Arrays.stream(intArray)
.boxed()
.map(e -> e * 2.andThen(e -> e * e)) // wrong! does not compile!
.forEach(System.out::println);
The reason is that in Java, lambdas and method references must have a target type, because they are not first citizens of the language. In fact, a lambda must be of a specific SAM type (single abstract method type). A SAM type is represented by a functional interface, i.e. an interface that has only one abstract method. Examples of functional interfaces are Function
, Predicate
, Consumer
, Supplier
, Runnable
, etc., or any SAM type declared by yourself, such as:
@FunctionalInterface
public interface Whatever<T, R> {
R thisIsMySingleAbstractMethod(T argument);
}
A lambda expression that matches the Whatever
functional type can be:
Whatever<Integer, String> whatever = number -> number.toString();
But the same lambda expression could have been used to declare a Function<Integer, String>
:
Function<Integer, String> whatever = number -> number.toString();
This example shows that lambdas have no type of their own until they are targeted to a SAM type. Only after this point, the methods of the type are available for chaining.
In a stream, the map
method expects a Function
as an argument. If you can express such function with an inlined lambda expression, then this lambda expression will be automatically inferred to be of the type of the argument of the map
method. This will happen (at least conceptually) after the lambda expression has been evaluated by the compiler.
However, if you want to use a method of the lambda expression's target type, (this would be andThen
in your example), it is not possible, because the compiler hasn't figured out the target type yet.
That's why casting the lambda expression works: it tells the compiler in advance what the target type of the lambda expression will be.