Generic type parameters inference in method chaining

Federico Peralta Schaffner :

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?
Eugene :

If this should really be a comment, please let me know and I'll break it in separate comments (it will probably not fit into a single one).

First poly expression is one that can have different types in different contexts. You declare something in contextA it has typeA; one in contextB and it has typeB.

What you are doing with your map creation via ImmutableMap.of("a", "b") or ImmutableMap.of(1,2) is such a thing. To be more precise Chapter 15.2 in the JLS says that this is actually a Class Instance Creation Expression poly expression.

So far we have established that A generic class instance creation is a poly expression. So that instance creation could have different types based on the context where it is used (and that obviously happens).

Now in your example with a ImmutableMap.builder things are not that difficult to infer (if you could chain builder and of for example) Builder is declared like this:

   public static <K, V> Builder<K, V> builder() {
      return new Builder<K, V>();
   }

and ImmutableMap.of like this:

   public static <K, V> ImmutableMap<K, V> of() {
      return ImmutableBiMap.of();
   }

Notice how both declare the same generic types K,V. This would probably be easy to pass from one method to another. But consider the case when your methods (let's say 10 methods) each declare different bound of the generic types, like:

<K,V> of()
....
<M extends T, R extends S> build()

And so on. And you can chain them. Information about types has to be passed from left to right and from right to left for the inference to work and as far as I can tell it would be very hard to do (besides being very complex).

Right now the way this works is that each poly expression is compiled one at a time from what I see (and your examples seems to prove that).

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=438520&siteId=1