When does Java require explicit type parameters?

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)); } } 

Operators with map1 and map3 compile fine, but map2 fails:

 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)) 

A compiler error can be resolved by providing explicit parameters like <String, String, Integer> .

  • When does Java 8 require explicit type parameters? Sense, is there a known pattern that violates the type of output?
  • Is it possible to change toImmutableMap() and MapCollectorBuilder to avoid explicit type parameters without losing the use of Builder to configure the collector?

UPDATE

  1. Why does the map3 operator map3 ? How is it different from the statement containing map2 ?
+13
java java-8
Jul 12 '15 at 16:39
source share
3 answers

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.

+3
Jul 13 '15 at 12:53
source share

The target type of lambda expression is fully contextualized as described in the Java tutorial . Therefore, lambdas do not contribute to the output of a type parameter; instead, they rely on him. Method references "are compact, easy-to-read lambda expressions for methods that already have a name" (Oracle Java Tutorial, emphasis added), so there is no difference where color analysis will be different when they are involved.

When you assign a link to a lambda / method to a variable, this type of variable provides a context for the output of type parameters. However, when you pass them directly to a generic method, you need another mechanism to infer their types. In some cases, other method arguments may serve this purpose. In your particular case, it looks like you probably need explicit type arguments:

 ImmutableMap<String, Integer> map2 = Stream.of("1", "2", "3").collect( Testcase.<String, String, Integer>toImmutableMap(i -> i, Integer::valueOf).build()); 

Update

Regarding the updated question, it seems that Java can correctly define types in the case of map3 partly because the definition is not complicated by calling the MapCollectorBuilder.build() method. Without build() the map3 type provides context for defining an argument of the first type Stream.collect() , which gives both K and V A parameter of type T can be inferred from the (assumed) type of Stream .

Nevertheless, with the participation of build() I think that Java shares the question of deriving type parameters for the universal toImmutableMap() method from the question of the return type of the build() call by its return value. In other words, he wants to determine the type of object returned by toImmutableMap() before he considers the type of value obtained by calling the method for that value.
+3
Jul 12 '15 at 17:40
source share

There is another way to fix this. You can give a hint to the compiler by explicitly specifying the type of the argument for the lambda identifier:

 ImmutableMap<String, Integer> map2 = Stream.of("1", "2", "3") .collect(Testcase.toImmutableMap((String i) -> i, Integer::valueOf).build()); 

Compiles in Javac 1.8.0_25 and ECJ 3.10.2.

+1
Jul 13 '15 at 4:42
source share



All Articles