问题
Say I have this code:
class Demo
{
static <T> T pick(T a1, T a2)
{
return a2;
}
public static void main(String[] args)
{
pick("d", 123);
}
}
From what I learned, it seems like I have stated that the two parameters a1
, a2
and the return type of pick
must be under the same generic type T
.
So why is the compiler allowing me to pass a String
and an Integer
to pick
?
回答1:
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.
回答2:
The compiler will walk down the inheritance tree of a1
and a2
to find a common ancestor, in this case Object
. Part of the reason you aren't seeing that is that you are discarding the return value. The following two versions won't compile:
String choice = pick("d", 123);
and
Integer choice = pick("d", 123);
The following will, however:
Object choice = pick("d", 123);
来源:https://stackoverflow.com/questions/54623214/java-generic-method-not-working-parameter-not-being-restricted