Java通用方法无法调用约束更宽松的另一个通用方法并返回其值

Mar*_*ark 8 java generics

我想这样做:

<T extends java.util.Date> T a(@Nonnull T... dates) {
    return b(dates);  // compile error
}

<T extends Comparable<T>> T b(T... comparables) {
   return comparables[0];
}
Run Code Online (Sandbox Code Playgroud)

但是除非我在其中插入强制类型转换,否则它无法编译a

<T extends java.util.Date> T a(@Nonnull T... dates) {
    return (T) b(dates);  // warning about unsafe cast in IntelliJ
}

<T extends Comparable<T>> T b(T... comparables) {
   return comparables[0];
}
Run Code Online (Sandbox Code Playgroud)

有趣的是,如果我从a它的泛型中删除它:

java.util.Date a(java.util.Date... dates) {
    return b(dates);
}

<T extends Comparable<T>> T b(T... comparables) {
   return comparables[0];
}
Run Code Online (Sandbox Code Playgroud)

而且,如果我将原始代码移植到Kotlin,它也可以工作(这使我认为这是Java的限制,而不是根本上不可知的东西):

fun <T: java.util.Date> a(dates: Array<T>): T {
    return b(dates);
}

fun <T: Comparable<T>> b(comparables: Array<T>): T {
    return comparables[0];
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:Java的类型系统有什么特殊之处可阻止此类型的编译?在我看来,Java编译器可以只在后台插入强制类型转换(据我了解,这就是在其他情况下实现泛型的方式)。

Jac*_* G. 5

无需强制转换即可将其编译。相反,可以在指定T必须扩展时使用有界通配符Comparable<T>

<T extends java.util.Date> T a(T... dates) {
    return b(dates);  // compiles fine
}

<T extends Comparable<? super T>> T b(T... comparables) {
    return comparables[0];
}
Run Code Online (Sandbox Code Playgroud)

请注意,Comparable<? super T>而不是Comparable<T>

正如Johannes Kuhn在他的评论中所指出的,Datewill 的子类隐式实现Comparable<Date>Comparable<DateSubclass>,因此需要Comparable<? super T>

有关更多信息,请参见:什么是PECS(生产者扩展了超级消费者)?


mer*_*ike 5

问题在于,可以使用以下类来调用第一个方法:

class MyDate extends Date {}
Run Code Online (Sandbox Code Playgroud)

然后,T在第一种方法中推断为MyDate,但T在第二种方法中不能推断为MyDate,因为MyDate它不扩展Comparable<MyDate>-它仅扩展Comparable<Date>...

因此,编译错误的根本原因是Java泛型是不变的。这就是为什么Kotlin(其泛型支持声明站点差异)接受代码而没有问题的原因。

要在Java中解决此问题,可以使用通配符类型,如Jacoc G.的答案所示。