明确指定通配符的上限时有区别吗?

pha*_*t0m 22 java generics wildcard jls language-lawyer

假设我有一个通用的class Generic<A extends BaseType>.

就Java语言规范而言,在以下两种类型声明之间是否存在显着差异?

Generic<?>
Generic<? extends BaseType>
Run Code Online (Sandbox Code Playgroud)

嵌套通配符怎么样?

List<Generic<?>>
List<Generic<? extends BaseType>>
Run Code Online (Sandbox Code Playgroud)

考虑到这一点,我认为这些是等价的.Generic指定type参数A具有BaseType上限.

因此,BaseType无论是否明确指定,通配符都应始终"自动"或"隐式"限制.

下面,我尝试调和我的直觉与JLS.


我找不到有关"隐式"边界的信息,所以我从查看子类型规则开始.

阅读有关子类型$ 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).

(强调我的)

捕获转换$ 5.1.10应用于Generic<?>Generic<? extends BaseType>; 我想我对新鲜的类型变量有相同的界限.捕获转换后,我可以使用"包含"规则来建立子类型.

对于第一个例子,通过

如果Ti是形式为?的通配符类型参数(第4.5.1节),则Si是一个新类型变量,其上限为Ui [A1:= S1,...,An:= Sn],其下限为null类型(§4.1).

由于A 1BaseType,新变量的上限为BaseType.

对于第二种情况,通过

如果Ti是表单的通配符类型参数?延伸的Bi,则Si是新鲜型变量,其上限是GLB的(Bi,UI [A1:= S1,......,An中:=锡]),其下限是null类型.

glb(V1,...,Vm)定义为V1&...&Vm.

我知道glb(BaseType, BaseType),这也是BaseType.

如此看来,之间的子类型关系Generic<?>,并Generic<? extends BaseType>转到根据JLS,这我的直觉相匹配两种方式.


对于嵌套通配符,我将使用"包含"规则:

类型参数T1被称为包含另一个类型参数T2,写为T2 <= T1,如果由T2表示的类型集合可证明是在以下规则的反身和传递闭包下由T1表示的类型集的子集(其中<:表示子类型(§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,并且唯一的规则是"类型"形式的右侧T <= T,因此这些规则似乎根本没有帮助.

我如何通过JLS 得到它List<Generic<?>>并且List<Generic<? extends BaseType>>是彼此的子类型?

Hol*_*ger 7

以你最初的问题从字面上看,"是否有一个显着的区别"之间Generic<?>Generic<? extends BaseType>,答案一定是,他们是不等价的.

JLS§4.5.1明确指出:

通配符? extends Object等同于无界通配符?.

因此,这将相当于? extends BaseTypeBaseTypeObject,但即使是这样,他们是等价的,但仍然承受显着的差异,例如在地方没有捕捉转换发生的情况:

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
Run Code Online (Sandbox Code Playgroud)

值得注意的是,与第一直觉相反,给定一个声明Generic<T extends BaseType>,指定Generic<? extends Object>与等效的一样有效Generic<?>.只要它们与类型参数的界限不可分辨,并且边界始终是子类型Object,? extends Object则通配符的边界是有效的,始终有效.

所以,如果我们有一个类型声明

interface NumberSupplier<N extends Number> extends Supplier<N> {}
Run Code Online (Sandbox Code Playgroud)

我们可以写

NumberSupplier<? extends Object> s1;
NumberSupplier<? extends Serializable> s2;
NumberSupplier<? extends BigInteger> s3;
Run Code Online (Sandbox Code Playgroud)

甚至

NumberSupplier<? extends CharSequence> s4;
Run Code Online (Sandbox Code Playgroud)

我们甚至可以在没有实际类型扩展NumberCharSequence使用的情况下实现它() -> null

但不是

NumberSupplier<? extends String> s5;
Run Code Online (Sandbox Code Playgroud)

作为StringNumber可证明不同.

当涉及到分配,我们可以使用的问题已经提到的子类型的规则得出结论,NumberSupplier<? extends BigInteger>是一个亚型NumberSupplier<? extends Object>,由于? extends BigInteger 含有 ? extends Object(也 ? extends Number),因为BigInteger是一个亚型ObjectNumber,但正如你正确地指出,这并不适用到类型参数不是通配符的参数化类型.

因此,如果我们根据§4.5.1的包含规则声明像是List<NumberSupplier<?>>,List<NumberSupplier<? extends Object>>或者List<NumberSupplier<? extends Number>>想要判断其中一个是否是其他子类型,那么唯一可以应用的规则是,当类型参数是相同的类型()时,但是,我们不需要子类型规则,因为所有这些列表类型都是相同的类型:T <= T

如果两个引用类型具有相同的二进制名称(第13.1节),并且它们的类型参数(如果有)相同,则递归应用此定义,它们是相同的编译时类型.

包含规则仍然是有用的,例如它允许断定Map<String,? extends Number>是的一个亚型Map<String,Integer>,因为对于第一类型参数String <= String适用和类型参数的第二类型参数被覆盖由一个通配符特定包含的规则.


所以,剩下的问题是,这条规则允许我们得出这样的结论NumberSupplier<?>,NumberSupplier<? extends Object>或者NumberSupplier<? extends Number>是同一类型,因此List<NumberSupplier<?>>,List<NumberSupplier<? extends Object>>List<NumberSupplier<? extends Number>>可分配给对方.

它似乎不是捕获转换,因为捕获转换意味着计算有效边界,但也为每个通配符创建一个"新类型变量",它绝对是一个不同的类型.但是没有其他规则涵盖通配符兼容性.或者我没有找到它.试图将规范与实际行为相匹配,javac产生了一些非常有趣的结果:

特定

interface MySupplier<S extends CharSequence&Appendable> extends Supplier<S> {}
Run Code Online (Sandbox Code Playgroud)

以下声明显然有效:

List<MySupplier<? extends CharSequence>> list1 = Collections.emptyList();
List<MySupplier<? extends Appendable>>   list2 = Collections.emptyList();
Run Code Online (Sandbox Code Playgroud)

因为在这两种情况下,通配符的绑定都是多余的,因为匹配S的绑定,我们可能会猜测它们实际上是相同的类型.

javac认为他们不是

list1 = list2; // compiler error
list2 = list1; // dito
Run Code Online (Sandbox Code Playgroud)

虽然任何涉及捕获转换的操作都会导致兼容类型,例如

list1.set(0, list2.get(0)); // no problem
list2.set(0, list1.get(0)); // no problem
Run Code Online (Sandbox Code Playgroud)

并且做出被拒绝的任务间接起作用:

List<MySupplier<?>> list3;
list3 = list1;
list2 = list3; // no problem
// or
list3 = list2;
list1 = list3; // again no problem
Run Code Online (Sandbox Code Playgroud)

但在这里,?并不等同于? extends Object:

List<MySupplier<? extends Object>> list4;
list4 = list1; // compiler error
list2 = list4; // dito
// or
list4 = list2; // dito
list1 = list4; // dito
Run Code Online (Sandbox Code Playgroud)

但同样,间接任务也起作用.

list4 = list3 = list1; // works
list1 = list3 = list4; // works
list4 = list3 = list2; // works
list2 = list3 = list4; // works
Run Code Online (Sandbox Code Playgroud)

所以无论规则javac在这里使用什么,它都不是传递性的,它排除了子类型关系,以及一般的"它是同一类型"规则.看起来,这是真正的un(der)指定,并直接影响实现.并且,正如当前实现的那样,?没有边界是特殊的,允许使用任何其他通配符类型不可能的赋值链.