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:
Type inference represents the Java compiler's ability to look at a method invocation and its corresponding declaration to check and determine the type argument(s). The inference algorithm checks the types of the arguments and, if available, assigned type is returned.
Generic Methods A type parameter, also known as a type variable, is an identifier that specifies a generic type name. The type parameters can be used to declare the return type and act as placeholders for the types of the arguments passed to the generic method, which are known as actual type arguments.
Multiple parameters You can also use more than one type parameter in generics in Java, you just need to pass specify another type parameter in the angle brackets separated by comma.
Generics means parameterized types. The idea is to allow type (Integer, String, … etc., and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With