After reading this question, I've started to think about generic methods in Java 8. Specifically, what happens with generic type parameters when methods are chained.
For this question, I will use some generic methods from Guava's ImmutableMap
, but my question is more general and can be applied to all chained generic methods.
Consider ImmutableMap.of
generic method, which has this signature:
public static <K, V> ImmutableMap<K, V> of(K k1, V v1)
If we use this generic method to declare a Map
, the compiler infers the generic types correctly:
Map<String, String> map = ImmutableMap.of("a", "b");
I'm aware that from Java 8 onwards, the compiler inference mechanism has been improved, i.e. it infers the generic types of methods from the context, which in this case is an assignment.
The context can also be a method call:
void someMethod(Map<String, String> map) {
// do something with map
}
someMethod(ImmutableMap.of("a", "b"));
In this case, the generic types of ImmutableMap.of
are inferred from the generic types of the argument of someMethod
, ie. Map<String, String> map
.
But when I attempt to use ImmutableMap.builder()
and chain methods to build my map, I get a compilation error:
Map<String, String> map = ImmutableMap.builder()
.put("a", "b")
.build(); // error here: does not compile
The error is:
Error:(...) java: incompatible types:
ImmutableMap<Object, Object> cannot be converted to Map<String, String>
(I've removed the packages names from the error message for the sake of clarity).
I understand the error and why it happens. The first method in the chain is ImmutableMap.builder()
and the compiler has no context to infer the type parameters, so it fallbacks to <Object, Object>
. Then, the ImmutableMap.Builder.put
method is invoked with arguments "a"
and "b"
and finally the ImmutableMap.Builder.build()
method is called, which returns an ImmutableMap<Object, Object>
. This is why I'm receiving the incompatible types error: when I attempt to assign this ImmutableMap<Object, Object>
instance to my Map<String, String> map
variable, the compiler complains.
I even know how to solve this error: I could either break the method chain into two lines, so that the compiler can now infer the generic type parameters:
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
Map<String, String> map = builder.put("a", "b").build();
Or I could explicitly provide the generic type parameters:
Map<String, String> map = ImmutableMap.<String, String>builder()
.put("a", "b")
.build();
So my question is not how to solve/workaround this, but why I do need to provide the generic type parameters explicitly when chaining generic methods. Or, in other words, why can't the compiler infer the generic type parameters in a method chain, especially when the method chain is at the right side of an assignment? If it were possible, would this break something else (I mean, related to the generics type system)?
EDIT:
There's a question asking the same, however the only answer it has doesn't clearly explain why the compiler doesn't infer the generic type parameters in a method chain. All it has is a reference to a small paragraph in the JSR-000335 Lambda Expressions for the JavaTM Programming Language Final Release for Evaluation (spec part D):
There has been some interest in allowing inference to "chain": in a().b(), passing type information from the invocation of b to the invocation of a. This adds another dimension to the complexity of the inference algorithm, as partial information has to pass in both directions; it only works when the erasure of the return type of a() is fixed for all instantiations (e.g. List). This feature would not fit very well into the poly expression model, since the target type cannot be easily derived; but perhaps with additional enhancements it could be added in the future.
So I think I might rephrase my original question as follows:
- What would these additional enhancements be?
- How is it that passing partial information in both directions would make the inference algorithm more complex?
- Why would this only work when the erasure of the return type of a() is fixed for all instantiations (e.g. List)? In fact, what does it mean that the erasure of the return type of a method is fixed for all instantiations?
- Why wouldn't this feature fit very well into the poly expression model? Actually, what is the poly expression model? And why couldn't the target type be easily derived in this case?