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

时间:2016-04-04 12:27:02

标签: java generics java-8

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

2 个答案:

答案 0 :(得分:183)

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>的超类型。由于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,而不是呼叫站点

事实上,我最近在博客上发表了这篇文章API design anti pattern。你应该(几乎)从不设计一个泛型方法来返回任意类型,因为你(几乎)永远不能保证推断类型将被传递。一个例外是类似Collections.emptyList()的方法,如果列表的空白(以及泛型类型擦除)是<T>的任何推断将起作用的原因:

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