这是来自第三方库API的真实示例,但已简化。
使用Oracle JDK 8u72编译
考虑以下两种方法:
<X extends CharSequence> X getCharSequence() {
return (X) "hello";
}
<X extends String> X getString() {
return (X) "hello";
}
两者均报告“未经检查的演员”警告 - 我明白了。困扰我的是为什么我可以打电话
Integer x = getCharSequence();
它编译?编译器应该知道Integer
没有实现CharSequence
。致电
Integer y = getString();
给出错误(如预期的那样)
incompatible types: inference variable X has incompatible upper bounds java.lang.Integer,java.lang.String
有人可以解释为什么这种行为会被视为有效?它会有用吗?
客户端不知道此调用是不安全的 - 客户端的代码在没有警告的情况下编译。为什么编译器不会警告/发出错误?
另外,它与这个例子有什么不同:
<X extends CharSequence> void doCharSequence(List<X> l) {
}
List<CharSequence> chsL = new ArrayList<>();
doCharSequence(chsL); // compiles
List<Integer> intL = new ArrayList<>();
doCharSequence(intL); // error
尝试传递List<Integer>
会出现错误,如预期的那样:
method doCharSequence in class generic.GenericTest cannot be applied to given types; required: java.util.List<X> found: java.util.List<java.lang.Integer> reason: inference variable X has incompatible bounds equality constraints: java.lang.Integer upper bounds: java.lang.CharSequence
如果报告为错误,为什么Integer x = getCharSequence();
不是?
答案 0 :(得分:183)
CharSequence
是interface
。因此,即使SomeClass
没有实现CharSequence
,也很有可能创建一个类
class SubClass extends SomeClass implements CharSequence
因此你可以写
SomeClass c = getCharSequence();
因为推断类型X
是交集类型SomeClass & CharSequence
。
Integer
的情况有点奇怪,因为Integer
是最终的,但final
在这些规则中不起任何作用。例如,你可以写
<T extends Integer & CharSequence>
另一方面,String
不是interface
,因此无法扩展SomeClass
以获取String
的子类型,因为java不支持类的多重继承。
使用List
示例,您需要记住泛型既不是协变也不是逆变。这意味着,如果X
是Y
的子类型,则List<X>
既不是子类型也不是List<Y>
的超类型。由于Integer
未实现CharSequence
,因此您无法在List<Integer>
方法中使用doCharSequence
。
然而,您可以将其编译为
<T extends Integer & CharSequence> void foo(List<T> list) {
doCharSequence(list);
}
如果你有一个返回的方法List<T>
,就像这样:
static <T extends CharSequence> List<T> foo()
你可以做到
List<? extends Integer> list = foo();
同样,这是因为推断的类型是Integer & CharSequence
,这是Integer
的子类型。
当您指定多个边界(例如<T extends SomeClass & CharSequence>
)时会隐式发生交叉类型。
有关详细信息,here是JLS的一部分,它解释了类型边界的工作原理。您可以包含多个接口,例如
<T extends String & CharSequence & List & Comparator>
但只有第一个绑定可能是非接口。
答案 1 :(得分:59)
在X
分配之前,编译器推断出的类型为Integer & CharSequence
。这种类型感觉很奇怪,因为Integer
是最终的,但它是Java中完全有效的类型。然后将其转换为Integer
,这完全可以。
Integer & CharSequence
类型只有一个可能的值:null
。通过以下实现:
<X extends CharSequence> X getCharSequence() {
return null;
}
以下作业将起作用:
Integer x = getCharSequence();
由于这个可能的价值,没有理由为什么分配应该是错误的,即使它显然是无用的。警告会很有用。
事实上,我最近在博客上发表了这篇文章API design anti pattern。你应该(几乎)从不设计一个泛型方法来返回任意类型,因为你(几乎)永远不能保证推断类型将被传递。一个例外是类似Collections.emptyList()
的方法,如果列表的空白(以及泛型类型擦除)是<T>
的任何推断将起作用的原因:
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}