Both String
and Integer
are subclasses of Object
, along with every other type in Java. When compiling a generic method (or class), Java attempts to find the closest supertype shared between every instance of the generic type. This part never fails, because Object
exists. But if a generic type is resolved to Object
, it may not be useful anymore.
So what’s the point in using generics here if the compiler will let any types to be used? It’s all to do with the return type. Assuming your definition of pick()
, what do you think will happen when you try to compile each of these lines?
Object o = pick("Hello", 123); // 1
String s = pick("Hello", 123); // 2
String s = pick("Hello", "world"); // 3
Integer i = pick("Hello", 123); // 4
Integer i = pick(123, 456); // 5
int i = pick(123, 456); // 6
1 compiles just fine, but then you’ve lost any useful type information. This is what would happen if you didn’t use generics at all, and instead just used Object
for everything. It’s you’d have had to do before Java 5, along with plentiful casting and exception catching.
2 and 4 won’t compile:
error: incompatible types: inferred type does not conform to upper bound(s)
Since the two arguments to pick()
share only Object
as a common supertype, T
becomes Object
and an Object
is returned and you can’t assign an Object
to a String
or an Integer
.
3 works just fine though. Both arguments have the same type, so T
is easily determined to be String
. 5 works for similar reasons.
6 also works, but not because T
becomes int
. int
is a primitive type, so it can’t be used in a generic. When attempting to resolve the generic type T
, the compiler first autoboxes the primitive arguments into a ‘real’ class (Integer
in this case). This also happens for 4 and 5, and even when you simply assign a literal like Integer i = 123;
. The difference here is that the result (an Integer
) is unboxed back to an int
so that it can be assigned to i
.
In your example implementation of pick()
, the return value should have the same type as the second parameter. If your API specifies that the result is always derived primarily from that parameter, you could use two generic types:
static <T, U> T pick(U a1, T a2) {
return a2;
}
With this addition, 2 still fails to compile, but 4 works as you’d expect.