Java can often infer generics based on the arguments (and even on the return type, in contrast to e.g. C#).
Case in point: I've got a generic class Pair<T1, T2>
which just stores a pair of values and can be used in the following way:
Pair<String, String> pair = Pair.of("Hello", "World");
The method of
looks just like this:
public static <T1, T2> Pair<T1, T2> of(T1 first, T2 second) {
return new Pair<T1, T2>(first, second);
}
Very nice. However, this no longer works for the following use-case, which requires wildcards:
Pair<Class<?>, String> pair = Pair.of((Class<?>) List.class, "hello");
(Notice the explicit cast to make List.class
the correct type.)
The code fails with the following error (provided by Eclipse):
Type mismatch: cannot convert from
TestClass.Pair<Class<capture#1-of ?>,String>
toTestClass.Pair<Class<?>,String>
However, explicitly calling the constructor still works as expected:
Pair<Class<?>, String> pair =
new Pair<Class<?>, String>((Class<?>) List.class, "hello");
Can someone explain this behaviour? Is it by design? Is it wanted? Am I doing something wrong or did I stumble upon a flaw in the design / bug in the compiler?
Wild guess: the “capture#1-of ?” somehow seems to imply that the wildcard is filled in by the compiler on the fly, making the type a Class<List>
, and thus failing the conversion (from Pair<Class<?>, String>
to Pair<Class<List>, String>
). Is this right? Is there a way to work around this?
For completeness’ sake, here is a simplified version of the Pair
class:
public final class Pair<T1, T2> {
public final T1 first;
public final T2 second;
public Pair(T1 first, T2 second) {
this.first = first;
this.second = second;
}
public static <T1, T2> Pair<T1, T2> of(T1 first, T2 second) {
return new Pair<T1, T2>(first, second);
}
}
The reason the constructor works is that you're explicitly specifying the type parameters. The static method also will work if you do that:
Of course, the whole reason you have a static method in the first place is probably just to get the type inference (which doesn't work with constructors at all).
The problem here (as you suggested) is that the compiler is performing capture conversion. I believe this is as a result of [§15.12.2.6 of the JLS]:
If you really want the inference, one possible workaround is to do something like this:
The variable
pair
will have a wider type, and it does mean a bit more typing in the variable's type name, but at least you don't need to cast in the method call anymore.