When does Java require explicit type parameters?
java, java-8
Solution
To answer your question “Meaning, is there a known pattern that breaks type inference?” shortly: of course, there is a pattern, moreover there is a huge specification for the entire behavior of the Java programming language.
But the chapters regarding type inference and method invocation types are really exhaustive and hard to understand. This is best illustrated by the fact that in the case of unexpected behavior, often large discussions about the expected behavior according to the specification occur.
But there are some points explainable and rememberable for a programmer.
There are two ways how to infer the type parameters, by the arguments passed to a method or parts from which an expression is composed or by the target type of an expression, that is, the expected type for a parameter of an invocation, the variable which is assigned or the return type of a method in case of a return statement.
The target type can get propagated through nested method invocations like in
TargetType x=foo(bar(/*target type can be used*/));
or in a conditional like
TargetType x=condition? foo(/*target type can be used*/): foo(/*target type can be used*/);
but not in case of a chained invocation 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, the `Stream.of(…)` and `.collect(…)` are chained, therefore the target type cannot be used to determine the stream type of the `of` invocation but the arguments provided to that method are sufficient to infer the type `Stream<String>`. The `collect` method provides the result that gets assigned to `map1`, so both, the stream type `Stream<String>`and the target type `ImmutableMap<String, Integer>` are known and can be used for the type inference for the expression. Onto the expressions:
`Testcase.toImmutableMap(keyMapper, valueMapper).build()` this is a chained invocation, so the target type is known for `build()` but not for `toImmutableMap`. However, the arguments to `toImmutableMap` are local variables which have a known exact type, therefore the type inference can use them to infer the result type of `toImmutableMap` and check whether it matches the expectations for `.build()`
`Testcase.toImmutableMap(i -> i, Integer::valueOf).build()` this is again a chained invocation but now the argument `i - > i` has an incomplete type and suffers from the absence of the target type. The attempt to guess a type for `i -> i` without knowledge about the target type fails.
`Testcase.toImmutableMap2(i -> i, Integer::valueOf)` this is not a chained invocation, therefore the target type is available for the `toImmutableMap2` call (in respect to the `collect` call, it’s a nested invocation). Therefore, the target type of `toImmutableMap2` allows to infer target types for the parameters, hence for the `i -> i` lambda expression. With a proper target type, the correct functional signature can be inferred.
Problem
Given: ``` import com.google.common.collect.ImmutableMap; import java.util.function.Function; import java.util.stream.Collector; import java.util.stream.Stream; public class Testcase { public static <T, K, V> MapCollectorBuilder<T, K, V> toImmutableMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends V> valueMapper) { return null; } public static final class MapCollectorBuilder<T, K, V> { public Collector<T, ?, ImmutableMap<K, V>> build() { return null; } } public static <T, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap2( Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends V> valueMapper) { return null; } public void main(String[] args) { Function<String, String> keyMapper = i -> i; Function<String, Integer> valueMapper = Integer::valueOf; ImmutableMap<String, Integer> map1 = Stream.of("1", "2", "3") .collect(Testcase.toImmutableMap(keyMapper, valueMapper).build()); ImmutableMap<String, Integer> map2 = Stream.of("1", "2", "3") .collect(Testcase.toImmutableMap(i -> i, Integer::valueOf).build()); ImmutableMap<String, Integer> map3 = Stream.of("1", "2", "3") .collect(Testcase.toImmutableMap2(i -> i, Integer::valueOf)); } } ``` The statements involving `map1` and `map3` compile fine, but `map2` fails with: ``` Testcase.java:[41,57] incompatible types: cannot infer type-variable(s) T,K,V (argument mismatch; invalid method reference no suitable method found for valueOf(java.lang.Object) method java.lang.Integer.valueOf(java.lang.String) is not applicable (argument mismatch; java.lang.Object cannot be converted to java.lang.String) method java.lang.Integer.valueOf(int) is not applicable (argument mismatch; java.lang.Object cannot be converted to int)) ``` The compiler error can be solved by providing explicit type parameters `<String, String, Integer>`. - When does Java 8 require explicit type parameters? Meaning, is there a known pattern that breaks type inference? - Can `toImmutableMap()` and `MapCollectorBuilder` be changed to avoid explicit type parameters without losing the use of a Builder for configuring the Collector? UPDATE: - Why does the statement involving `map3` work? How does it differ from the statement involving `map2`?