假设我有一个通用class Generic<A extends BaseType>
。
就Java语言规范而言,在以下两种类型声明之间是否存在显着差异?
Generic<?>
Generic<? extends BaseType>
嵌套通配符怎么样?
List<Generic<?>>
List<Generic<? extends BaseType>>
考虑到这一点,我认为这些是等价的。 Generic
指定类型参数A
的上限为BaseType
。
因此,无论我是否明确指定,通配符都应始终由BaseType
“自动”或“隐式”限制。
下面,我尝试调整我的直觉与JLS。
我无法找到有关“隐式”边界的信息,所以我开始查看子类型规则。
阅读关于subtyping $4.10.2的JLS部分,它说:
给定泛型类型声明
C<F1,...,Fn>
(n> 0),参数化类型C<T1,...,Tn>
的直接超类型,其中Ti(1≤i≤n)是一种类型,是以下所有内容:
D<U1 θ,...,Uk θ>
,其中D<U1,...,Uk>
是泛型类型,是泛型类型C<T1,...,Tn>
的直接超类型,θ是替换[F1:= T1,... ,FN:= Tn的
C<S1,...,Sn>
,其中Si含有Ti(1≤i≤n)(§4.5.1)。
(强调我的)
据我所知,“通配符”在JLS中不被视为“类型”。所以这不适用于前两个,但它适用于两个List
示例。
相反,这应该适用:
给定泛型类型声明
C<F1,...,Fn>
(n> 0),参数化类型C<R1,...,Rn>
的直接超类型,其中至少一个Ri(1≤i≤n)是通配符类型参数,是参数化类型C<X1,...,Xn>
的直接超类型,它是将捕获转换应用于C<R1,...,Rn>
(§5.1.10)的结果。
(强调我的)
将capture conversion $5.1.10应用于Generic<?>
和Generic<? extends BaseType>
;我想我对新鲜的类型变量有相同的界限。捕获转换后,我可以使用“包含”规则来建立子类型。
对于第一个例子,通过
如果Ti是形式为?的通配符类型参数(第4.5.1节),那么Si是一个新的类型变量,其上限是Ui [A1:= S1,...,An:= Sn]并且其下限是null类型(§4.1)。
由于 A 1 为BaseType
,因此新变量的上限为BaseType
。
对于第二种情况,通过
如果Ti是表单的通配符类型参数?扩展Bi,然后Si是一个新的类型变量,其上限是glb(Bi,Ui [A1:= S1,...,An:= Sn]),其下限是null类型。
glb(V1,...,Vm)定义为V1&amp; ......&amp; VM。
我得到glb(BaseType, BaseType)
,再次是BaseType
。
因此,似乎Generic<?>
和Generic<? extends BaseType>
之间的子类型关系根据JLS进行了两种方式,这符合我的直觉。
对于嵌套通配符,我会使用"contains" rule:
类型参数T1据说包含另一个类型参数T2, 如果由T2表示的类型集合可证明为a,则写入T2&lt; = T1 在反身和反射下由T1表示的类型集的子集 传递性关闭以下规则(其中&lt;:表示子类型 (§4.10)):
? extends T <= ? extends S if T <: S
? extends T <= ?
? super T <= ? super S if S <: T
? super T <= ?
? super T <= ? extends Object
T <= T
T <= ? extends T
T <= ? super T
结合
C<S1,...,Sn>, where Si contains Ti (1 ≤ i ≤ n) (§4.5.1).
从上面,我得到:
如果List<Generic<?>>
包含List<Generic<? extends BaseType>>
,则 Generic<?>
是Generic<? extends BaseType>>
的直接超类型
虽然,我看不到我如何使用包含规则。根据规则,我可以使用的唯一附加信息是子类型。我已经知道子类型在两种类型之间是双向的。
虽然如果包含两者之间的子类型是答案,我还可以证明List<String>
是List<Object>
的子类型,它不是也不应该是。
此外,我需要显示Type <= OtherType
形式的某些内容,并且“type”形式右侧的唯一规则是T <= T
,因此这些规则似乎没有一点帮助。
如何通过JLS让List<Generic<?>>
和List<Generic<? extends BaseType>>
成为彼此的子类型?
答案 0 :(得分:6)
从字面上看你的初步问题,Generic<?>
和Generic<? extends BaseType>
之间是否存在“显着差异”,答案必须是,它们并不等同。
JLS §4.5.1明确指出:
通配符
? extends Object
等同于无界通配符?
。
仅当? extends BaseType
为BaseType
时,它才相当于Object
,但即便如此,它们也是等效的,但仍然存在明显的差异,例如在没有发生捕获转换的地方:
boolean b1 = new Object() instanceof Supplier<?>; // valid code
boolean b2 = new Object() instanceof Supplier<? extends Object>; // invalid
Supplier<?>[] array1; // valid declaration
Supplier<? extends Object>[] array1; // invalid
值得注意的是,与第一个直觉相反,给定一个声明Generic<T extends BaseType>
,指定Generic<? extends Object>
与等效Generic<?>
一样有效。通配符的边界是有效的,只要它们与类型参数的界限不是可证明不同,并且由于边界始终是Object
的子类型,? extends Object
始终有效。
因此,如果我们有类型声明,如
interface NumberSupplier<N extends Number> extends Supplier<N> {}
我们可以写
NumberSupplier<? extends Object> s1;
NumberSupplier<? extends Serializable> s2;
NumberSupplier<? extends BigInteger> s3;
甚至
NumberSupplier<? extends CharSequence> s4;
我们甚至可以使用Number
CharSequence
和() -> null
的情况下实现它
但不是
NumberSupplier<? extends String> s5;
String
和Number
可证明不同。
说到分配时,我们可以使用问题中已经引用的子类型规则得出结论:NumberSupplier<? extends BigInteger>
是NumberSupplier<? extends Object>
的子类型,因为? extends BigInteger
包含 ? extends Object
(以及包含 ? extends Number
),因为BigInteger
是Object
和Number
的子类型,但是当你正确指出,这不适用于类型参数不是通配符的参数化类型。
因此,如果我们有List<NumberSupplier<?>>
,List<NumberSupplier<? extends Object>>
或List<NumberSupplier<? extends Number>>
这样的声明,并想根据§4.5.1的包含<来判断其中一个是否是其他的子类型/ em>规则,唯一可以应用的规则是,当类型参数是相同的类型(T <= T
)时,但是,我们不需要子类型规则,因为那时,所有这些列表类型are the same type:
两个引用类型是相同的编译时类型,如果它们具有相同的二进制名称(第13.1节),并且它们的类型参数(如果有的话)相同,则递归应用此定义。
包含规则仍然有用,例如它允许得出结论:Map<String,? extends Number>
是Map<String,Integer>
的子类型,因为第一个类型参数String <= String
适用,第二个类型参数的类型参数由特定于通配符覆盖包含规则。
所以剩下的问题是,哪条规则允许我们得出结论:NumberSupplier<?>
,NumberSupplier<? extends Object>
或NumberSupplier<? extends Number>
是相同的类型,因此List<NumberSupplier<?>>
,{{1 }},或List<NumberSupplier<? extends Object>>
可彼此分配。
它似乎不是捕获转换,因为捕获转换意味着计算有效边界,但也为每个通配符创建一个“新类型变量”绝对不同的类型。但是没有其他规则涵盖通配符兼容性。或者我没有找到它。试图将规范与List<NumberSupplier<? extends Number>>
的实际行为相匹配有一些非常有趣的结果:
鉴于
javac
以下声明显然有效:
interface MySupplier<S extends CharSequence&Appendable> extends Supplier<S> {}
因为在这两种情况下,通配符的绑定都是List<MySupplier<? extends CharSequence>> list1 = Collections.emptyList();
List<MySupplier<? extends Appendable>> list2 = Collections.emptyList();
绑定的一个,我们可能会猜测它们实际上是相同的类型。
但S
认为他们不是
javac
虽然任何涉及捕获转换的操作都会产生兼容类型,例如
list1 = list2; // compiler error
list2 = list1; // dito
并且间接执行被拒绝的作业:
list1.set(0, list2.get(0)); // no problem
list2.set(0, list1.get(0)); // no problem
但是在这里,List<MySupplier<?>> list3;
list3 = list1;
list2 = list3; // no problem
// or
list3 = list2;
list1 = list3; // again no problem
不等同于?
:
? extends Object
但是,间接作业再次发挥作用。
List<MySupplier<? extends Object>> list4;
list4 = list1; // compiler error
list2 = list4; // dito
// or
list4 = list2; // dito
list1 = list4; // dito
所以无论list4 = list3 = list1; // works
list1 = list3 = list4; // works
list4 = list3 = list2; // works
list2 = list3 = list4; // works
在这里使用什么规则,它都不是传递性的,它排除了子类型关系,以及一般的“它是同一类型”规则。看起来,这是真正的un(der)指定,并直接影响实现。并且,正如当前实现的那样,javac
无边界是特殊的,允许使用任何其他通配符类型无法实现的赋值链。