Mat*_*ace 7 java generics covariance
我对通配符边界的规则感到困惑.似乎有时可以声明一个方法参数,其边界不满足类声明的边界.在下面的代码中,方法foo(...)编译很好,但bar(...)没有编译.我不明白为什么要允许任何一个.
public class TestSomething {
    private static class A<T extends String> {}
    public static void foo(A<? extends Comparable<?>> a) {
    }
    public static void bar(A<? extends Comparable<Double>> a) {
    }
}
我们先来考虑方法void foo(A<? extends Comparable<?>> a).A<? extends Comparable<?>>是"兼容" A<T extends String>因为存在通配符类型P,并且具有可比较的通配符类型Q以满足以下条件:
P <: Comparable<Q> && P <: String
因为String <: Comparable<String>,Q必须是String,并且P可能是任何子类型String(由于声明了String final,因此您的选项有限)
现在让我们考虑方法void bar(A<? extends Comparable<Double>> a).没有P可以满足的通配符类型
P <: Comparable<Double> && P <: String
因为String已经实现了Comparable<String>不是a Comparable<Double>,并且任何子类都不可能String实现Comparable<Double>.
仅仅因为你写了一个签名A<? extends Comparable<?>> a并不意味着你可以传递任何方法A<? extends Comparable<?>>.您可以将声明更改为接受任何,A<? extends Object>并且它也将编译,但您只能实例化A<T extends String>,因此它不是必须使用String或其子类的漏洞.
有趣的是,我的Eclipse IDE甚至没有在上面声明的bar中找到编译错误,但是如果bar接受A<? extends Integer>了它就会这样做.
有关完整的理解,请参阅Java规范的这一部分.
如果满足下列条件之一,则两个类型参数可证明是不同的:
类型参数T1被称为包含另一个类型参数T2,写为T2 <= T1,如果由T2表示的类型集合可证明是在以下规则的反身和传递闭包下由T1表示的类型集的子集(其中<:表示子类型(§4.10)):
? extends T <= ? extends S if T <: S
? super T <= ? super S if S <: T
T <= T
T <= ? extends T
T <= ? super T
这是一个参数化类型何时“格式良好”的问题,即允许使用什么类型的参数。JLS 在这个主题上写得不是很好,而且编译器正在做不符合规范的事情。以下是我的理解。(根据 JLS8、Oracle javac 8)
一般来说,我们谈论通用类/接口声明G<T extends B1>;举个例子
    class Foo<T extends Number> { .. }
泛型声明可以看作是一组具体类型的声明;例如Foo声明类型Foo<Number>, Foo<Integer>, Foo<Float>, ...
具体类型G<X>(其中 X 是类型)是格式正确的 iff X<:B1,即X是 的子类型B1。
Foo<Integer>是格式良好的,因为Integer<:Number.Foo<String>格式不正确;它不存在于类型系统中。这个约束是严格执行的,例如,这不会编译
    <T> void m1(Foo<T> foo)  // Error, it's not the case that T<:Number
给定一个类型G<? super B2>,我们会期望这样B2<:B1。这是因为我们最常需要对其应用捕获转换,导致G<X> where B2<:X<:B1,意味着B2<:B1。如果B2<:B1为假,我们将在类型系统中引入矛盾,导致奇怪的行为。
事实上,Foo<? super String>被 javac 拒绝,这很好,因为该类型显然是程序员的错误。
有趣的是,我们在 JLS 中找不到这个约束;或者至少,JLS 中没有明确说明。实验表明,javac并不总是强制执行此约束,例如
    <T> Foo<? super T> m2()  // compiles, even though T<:Number is false
    <String>m2();  // compiles! returns Foo<? super String> !
目前还不清楚为什么他们被允许。但我不知道这在实践中会导致任何问题。
给定G<? extends B2>,捕获转化率G<X> where X<:B1&B2。
问题是交集类型何时B1&B2是格式良好的。最自由的方法是允许任何交叉点;即使交集为空,即B1&B2相当于空类型,也不会导致类型系统出现问题。
但实际上,我们希望编译器拒绝诸如Number&String、 等引入的内容Foo<? extends String>,因为它很可能是程序员的错误。
更具体的原因是javac需要构造一个“概念类”,它是 的子类型,B1&B2以便 javac 可以推断可以在该类型上调用哪些方法。为此目的,Number&String不能 被允许,而Number&Integer、Number&Object等Number&Runnable是允许的。这部分在JLS#4.9中指定
String & Comparable<Double>不允许,因为概念类将同时实现Comparable<String>和Comparable<Double>,这在 Java 中是非法的。
B1并且B2可以有多种形式,从而导致更复杂的情况。这是规范没有经过深思熟虑的地方。例如,从规范的文本来看,不清楚如果其中一个是类型变量怎么办?我们的行为javac确实合理
    <T extends Runnable> Foo<? extends T> m3()  // error
    <T extends Object  > Foo<? extends T> m4()  // error
    <T extends Number  > Foo<? extends T> m5()  // ok
    <T extends Integer > Foo<? extends T> m6()  // ok
另一个例子,应该Number & Callable<?>允许吗?如果是的话,概念类的超级接口应该是什么?请记住,Callable<?>不能是超级接口
    class Bar extends Number implements Callable<?> // illegal
在更复杂的情况下,我们有类似捕获转换引入的类型变量的Foo<Number> & Foo<CAP#1>情况。CAP#1规范明确禁止它,但用例表明它应该是合法的。
javac处理这些案件比 JLS 更自由。查看Maurizio和Dan的回复
那么,作为程序员的我们该怎么办呢?- 遵循您的直觉并构建对您有意义的类型。很可能javac会接受。如果不是,则可能是您的错误。在极少数情况下,该类型是有意义的,但规范/javac 不允许这样做;你运气不好:)你必须找到解决方法。
| 归档时间: | 
 | 
| 查看次数: | 569 次 | 
| 最近记录: |