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

2022-08-31 06:55:42

这是来自第三方库 API 的真实示例,但进行了简化。

使用 Oracle JDK 8u72 编译

请考虑以下两种方法:

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

<X extends String> X getString() {
    return (X) "hello";
}

两者都报告了“未经检查的演员”警告 - 我明白为什么。让我感到困惑的是,为什么我可以打电话

Integer x = getCharSequence();

它编译?编译器应该知道 不实现 。调用IntegerCharSequence

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();


答案 1

CharSequence是一个 .因此,即使不实现,也完全有可能创建一个类interfaceSomeClassCharSequence

class SubClass extends SomeClass implements CharSequence

因此,您可以编写

SomeClass c = getCharSequence();

因为推断的类型是交集类型。XSomeClass & CharSequence

这有点奇怪,因为这是最终的,但在这些规则中不起任何作用。例如,您可以编写IntegerIntegerfinal

<T extends Integer & CharSequence>

另一方面,不是 一个 ,因此不可能扩展以获得 的子类型,因为 java 不支持类的多重继承。StringinterfaceSomeClassString

通过这个例子,你需要记住泛型既不是协变的,也不是逆变的。这意味着 if 是 的子类型,既不是 的子类型,也不是 的超类型。由于 不实现 ,因此不能在方法中使用。ListXYList<X>List<Y>IntegerCharSequenceList<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 & CharSequenceInteger

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

有关更多信息,下面是 JLS 的部分,其中解释了类型边界的工作原理。您可以包含多个接口,例如

<T extends String & CharSequence & List & Comparator>

但只有第一个边界可以是非接口。


答案 2

编译器在 赋值之前推断的类型是 。这种类型感觉很奇怪,因为它是最终的,但它在Java中是完全有效的类型。然后将其转换为 ,这是完全可以的。XInteger & CharSequenceIntegerInteger

该类型只有一个可能的值:。通过以下实现:Integer & CharSequencenull

<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;
}

推荐