泛型返回类型上限-接口与类-令人惊讶的有效代码

这是一个来自第三方库 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();没有?

29309 次浏览

CharSequenceinterface。因此,即使 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示例,您需要记住泛型既不是协变的,也不是逆变的。这意味着,如果 XY的子类型,那么 List<X>既不是 List<Y>的子类型,也不是 List<Y>的超类型。由于 Integer不实现 CharSequence,所以不能在 doCharSequence方法中使用 List<Integer>

但是,您可以编译这个

<T extends Integer & CharSequence> void foo(List<T> list) {
doCharSequence(list);
}

如果你有一个像这样的 报税表 a List<T>方法:

static <T extends CharSequence> List<T> foo()

你可以的

List<? extends Integer> list = foo();

同样,这是因为推断的类型是 Integer & CharSequence,而这是 Integer的一个子类型。

当您指定多个边界(例如 <T extends SomeClass & CharSequence>)时,交叉类型会隐式出现。

对于进一步的信息,给你是 JLS 的一部分,它解释了类型界限是如何工作的。您可以包括多个接口,例如。

<T extends String & CharSequence & List & Comparator>

但只有第一个界限可能是非接口。

在为 X赋值之前,编译器推断的类型是 Integer & CharSequence。这种类型 感觉很奇怪,因为 Integer是 final,但它在 Java 中是一种完全有效的类型。然后它被投向 Integer,这是完全可以的。

Integer & CharSequence类型只有一个可能的值: null:

<X extends CharSequence> X getCharSequence() {
return null;
}

下列任务将有效:

Integer x = getCharSequence();

由于这个可能的值,赋值没有理由是错误的,即使它显然是无用的。警告会有用的。

真正的问题是 API,而不是调用站点

事实上,我最近在博客上写过关于这个 API 设计反模式的文章。您应该(几乎)永远不要设计一个泛型方法来返回任意类型,因为您(几乎)永远不能保证推断的类型将被传递。一个例外是像 Collections.emptyList()这样的方法,在这种情况下,列表的空白(和泛型类型擦除)是为什么任何对 <T>的推断都会起作用的原因:

public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}