显式指定通配符的上限时是否有区别?

2022-09-02 02:00:20

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

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

Generic<?>
Generic<? extends BaseType>

嵌套通配符呢?

List<Generic<?>>
List<Generic<? extends BaseType>>

考虑到这一点,我认为这些是等效的。 指定类型参数具有上限。GenericABaseType

因此,通配符应始终由 “自动” 或 “隐式” 限定,无论我是否显式指定它。BaseType

下面,我试图将我的直觉与JLS相协调。


我找不到有关“隐式”边界的信息,因此我首先查看了子类型规则。

阅读有关子类型$ 4.10.2的JLS部分,它说:

给定一个泛型类型声明 (n > 0),参数化类型的直接超类型 ,其中 Ti (1 ≤ i ≤ n) 是一个类型,它们都如下:C<F1,...,Fn>C<T1,...,Tn>

  • D<U1 θ,...,Uk θ>,其中 是泛型类型,它是泛型类型的直接超类型,θ 是替换 [F1:=T1,...,Fn:=Tn]。D<U1,...,Uk>C<T1,...,Tn>

  • C<S1,...,Sn>,其中 Si 包含 Ti (1 ≤ i ≤ n) (§4.5.1)。

(强调我的)

据我所知,“通配符”在JLS中不被视为“类型”。所以这不能适用于前两个例子,但它适用于这两个例子。List

相反,这应该适用:

给定泛型类型声明 (n > 0),参数化类型的直接超类型(其中至少一个 Ri (1 ≤ i ≤ n) 是通配符类型参数,是参数化类型的直接超类型,它是将捕获转换为 (§5.1.10) 的结果。C<F1,...,Fn>C<R1,...,Rn>C<X1,...,Xn>C<R1,...,Rn>

(强调我的)

捕获转换 $5.1.10 应用于 和 ;我想我在新鲜类型变量上得到了相同的界限。捕获转换后,我可以使用“包含”规则来建立子类型。Generic<?>Generic<? extends BaseType>

对于第一个示例,通过

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

因为 A1 是 ,所以 fresh 变量的上限为 。BaseTypeBaseType

对于第二种情况,通过

如果 Ti 是以下形式的通配符类型参数?扩展 Bi,则 Si 是一个新类型变量,其上限为 glb(Bi, Ui[A1:=S1,...,An:=Sn]),下限为空类型。

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

我得到,这又是.glb(BaseType, BaseType)BaseType

因此,根据JLS,和之间的子类型关系似乎是双向的,这与我的直觉相匹配。Generic<?>Generic<? extends BaseType>


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

如果 T2 表示的类型集可证明是 T1 在以下规则的自反和传递闭包下表示的类型集的子集(其中<:表示子类型 (§4.10)),则类型参数 T1 称为包含另一个类型参数 T2,编写为 T2 <= T1:

  • ? 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<?>>是 if 包含的直接超类型List<Generic<? extends BaseType>>Generic<?>Generic<? extends BaseType>>

虽然,我不明白我如何使用包含规则。根据规则,我唯一可以使用的其他信息是子类型。我已经知道子类型在两种类型之间是双向的。

虽然,如果两者之间包含子类型是答案,我也可以证明这是一个子类型,它不是也不应该是。List<String>List<Object>

此外,我需要显示一些形式,并且具有表单“类型”右侧的唯一规则是 ,因此这些规则似乎根本没有帮助。Type <= OtherTypeT <= T

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


答案 1

从字面上看你最初的问题,和之间是否“有明显的区别”,答案一定是,它们不是等价的。Generic<?>Generic<? extends BaseType>

JLS §4.5.1 明确规定:

通配符等效于无界通配符 。? extends Object?

因此,它仅等效于 is ,但即使这样,它们也是等效的,但仍然具有显着的差异,例如,在没有发生捕获转换的地方:? 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

值得注意的是,与第一种直觉相反,给定一个声明,指定与等效的一样有效。通配符的边界是有效的,只要它们不能证明与类型参数的边界不同,并且由于边界始终是 的子类型,因此始终有效。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;

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

但不是

NumberSupplier<? extends String> s5;

作为 和 可证明是不同的StringNumber

当涉及到赋值时,我们可以使用问题中已经引用的子类型规则来得出结论,它是 的子类型,因为包含(并且还包含 ),因为 是 和 的子类型,但正如您正确指出的那样,这不适用于类型参数不是通配符的参数化类型。NumberSupplier<? extends BigInteger>NumberSupplier<? extends Object>? extends BigInteger? extends Object? extends NumberBigIntegerObjectNumber

因此,如果我们有类似 、 或 的声明,并且想要根据 §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> {}

以下声明显然是有效的:

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

因此,无论规则在这里使用什么,它都不是可传递的,这排除了子类型关系,以及一般的“它是同一类型”规则。看起来,这确实是未指定的,并且直接影响实现。而且,按照当前实现,无边界是特殊的,允许使用任何其他通配符类型都不可能的赋值链。javac?


答案 2

推荐