To answer your question "Sense, is there a known pattern that interrupts type inference?" in the near future: of course, there is a template, moreover, there is a huge specification for all the behavior of the Java programming language.
But the chapters dealing with inference types and method invocation types are indeed comprehensive and hard to understand. This is best illustrated by the fact that in the case of unexpected behavior, there is often great discussion about the expected behavior in accordance with the specification.
But there are some points explained and remembered for the programmer.
There are two ways to determine the type parameters, the arguments passed to the method or parts of which the expression is composed, or the target type of the expression, that is, the expected type for the call parameter, the variable that is assigned, or the return type of the method in the case of the return statement.
The type of target can propagate through nested method calls, such as
TargetType x=foo(bar(/*target type can be used*/));
or in conditional terms
TargetType x=condition? foo(/*target type can be used*/): foo(/*target type can be used*/);
but not in the case of a chain call, as in
TargetType x=foo(/*target type can NOT be used*/).foo();
Now to your examples:
ImmutableMap<String, Integer> map1 = Stream.of("1", "2", "3").collect( expression );
Here, Stream.of(…) and .collect(…) bound, so the target type cannot be used to determine the type of the call flow of , but the arguments provided to this method are sufficient to output the type Stream<String> . The collect method provides a result that assigned to map1 , therefore both stream types Stream<String> and the target type ImmutableMap<String, Integer> known and can be used to deduce the type for the expression. On expressions:
Testcase.toImmutableMap(keyMapper, valueMapper).build() This is a chain call, so the target type is known by build() , but not for toImmutableMap . However, the arguments toImmutableMap are local variables that have a known exact type, so type inference can use them to output the result type toImmutableMap and check if it matches the expectations for .build()
Testcase.toImmutableMap(i -> i, Integer::valueOf).build() is again a chain call, but now the argument i - > i has an incomplete type and suffers from the absence of the target type. Trying to guess the type for i -> i without knowing the type of target does not work.
Testcase.toImmutableMap2(i -> i, Integer::valueOf) is not a chain call, so the target type is available to call toImmutableMap2 (relative to the collect call, its nested call). Therefore, the target type toImmutableMap2 allows you to output target types for parameters, therefore, for the expression i -> i lambda. With the right type of goal, you can determine the correct functional signature.