说我有这个代码:
class Demo
{
static <T> T pick(T a1, T a2)
{
return a2;
}
public static void main(String[] args)
{
pick("d", 123);
}
}
据我了解,似乎我已经说过两个参数a1
a2
和返回类型pick
必须在相同的通用类型{{1}下}。
那为什么编译器允许我将T
和String
传递给Integer
?
答案 0 :(得分:5)
String
和Integer
都是Object
的子类,以及Java中的其他所有类型。编译通用方法(或类)时,Java会尝试查找在通用类型的每个实例之间共享的最接近的超类型。这部分永远不会失败,因为Object
存在。但是,如果将通用类型解析为Object
,则可能不再有用。
那么,如果编译器允许使用任何类型,那么在这里使用泛型有什么意义呢?这与返回类型有关。假设您定义了pick()
,那么当您尝试编译这些行中的每行时,您认为会发生什么?
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 可以正常编译,但是您丢失了所有有用的类型信息。如果您根本不使用泛型,而是只对所有内容使用Object
,就会发生这种情况。在Java 5之前,您必须要做的事情,还有大量的转换和异常捕获。
2 和 4 无法编译:
error: incompatible types: inferred type does not conform to upper bound(s)
由于pick()
的两个参数仅共享Object
作为公共超类型,因此T
变成Object
并返回Object
,而您不能将Object
分配给String
或Integer
。
3 效果很好。两个参数具有相同的类型,因此很容易确定T
为String
。 5 的工作原理类似。
6 也可以,但是不是,因为T
变成了int
。 int
是原始类型,因此不能在泛型中使用。尝试解析通用类型T
时,编译器首先将原始参数autoboxes转换为“真实”类(在这种情况下为Integer
)。即使在 4 和 5 中也是如此,即使您只是简单地分配了Integer i = 123;
之类的文字。此处的区别在于,结果(Integer
被取消装箱返回到int
,以便可以将其分配给i
。
在pick()
的示例实现中,返回值应与第二个参数具有相同的类型。如果您的API指定结果始终总是主要从该参数派生,则可以使用两种通用类型:
static <T, U> T pick(U a1, T a2) {
return a2;
}
添加了此功能后, 2 仍无法编译,但是 4 可以正常工作。
答案 1 :(得分:2)
编译器将沿着a1
和a2
的继承树查找共同的祖先,在本例中为Object
。您未看到的部分原因是您正在丢弃返回值。以下两个版本无法编译:
String choice = pick("d", 123);
和
Integer choice = pick("d", 123);
但是,以下内容将:
Object choice = pick("d", 123);