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

Is there a difference when specifying upper bounds for wildcards explicitly?

假设我有一个通用的class Generic

就Java语言规范而言,在以下两种类型声明之间有显著的区别吗?

1
2
Generic<?>
Generic<? extends BaseType>

嵌套通配符呢?

1
2
List<Generic<?>>
List<Generic<? extends BaseType>>

考虑到这一点,我假设这些是等价的。Generic指定类型参数A具有上界的BaseType

因此,通配符应该总是"自动"或"隐式"由BaseType限定,无论我是否明确指定它。

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

我找不到关于"隐式"界限的信息,所以我先看一下子类型规则。

在阅读有关4.10.2美元子类型的jls部分时,它说:

Given a generic type declaration C (n > 0), the direct supertypes of the parameterized type C, where Ti (1 ≤ i ≤ n) is a type, are all of the following:

  • D, where D is a generic type which is a direct supertype of the generic type C and θ is the substitution [F1:=T1,...,Fn:=Tn].

  • C, where Si contains Ti (1 ≤ i ≤ n) (§4.5.1).

(强调我的)

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

相反,这应该适用于:

Given a generic type declaration C (n > 0), the direct supertypes of the parameterized type C where at least one of the Ri (1 ≤ i ≤ n) is a wildcard type argument, are the direct supertypes of the parameterized type C which is the result of applying capture conversion to C (§5.1.10).

(强调我的)

将捕获转换$5.1.10应用于GenericGeneric;我认为在新类型变量上得到相同的界限。在捕获转换之后,我可以使用"包含"规则来建立子类型。

对于第一个示例,via

If Ti is a wildcard type argument (§4.5.1) of the form ?, then Si is a fresh type variable whose upper bound is Ui[A1:=S1,...,An:=Sn] and whose lower bound is the null type (§4.1).

由于a1is BaseType,fresh变量具有BaseType的上界。

对于第二种情况,通过

If Ti is a wildcard type argument of the form ? extends Bi, then Si is a fresh type variable whose upper bound is glb(Bi, Ui[A1:=S1,...,An:=Sn]) and whose lower bound is the null type.

glb(V1,...,Vm) is defined as V1 & ... & Vm.

我得到了glb(BaseType, BaseType),它又是BaseType

因此,根据jls,似乎GenericGeneric之间的子类型关系是双向的,这符合我的直觉。

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

A type argument T1 is said to contain another type argument T2,
written T2 <= T1, if the set of types denoted by T2 is provably a subset of the set of types denoted by T1 under the reflexive and transitive closure of the following rules (where <: denotes subtyping (§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, where Si contains Ti (1 ≤ i ≤ n) (§4.5.1).

从上面,我得到:

如果Generic中含有Generic>的话,List>List>的直接超型。

不过,我不知道如何使用包含规则。根据规则,我唯一能使用的附加信息是子类型。我已经知道子类型在这两种类型之间是双向的。

虽然,如果两者之间包含子类型是答案,我也可以证明ListList的一个子类型,它不是也不应该是。

此外,我还需要展示一些形式的Type <= OtherType,并且形式"type"右边唯一的规则是T <= T,所以这些规则似乎根本没有帮助。

我如何通过jls得到List>List>是彼此的子类型?


从字面上看,GenericGeneric之间是否存在"显著差异",答案必须是,它们不是等价的。

JLS第4.5.1条明确规定:

The wildcard ? extends Object is equivalent to the unbounded wildcard ?.

因此,只有当BaseTypeObject时,它才相当于? extends BaseType,但即使如此,它们也是相等的,但仍有显著差异,例如在没有捕获转换的地方:

1
2
3
4
5
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,指定Generic与等效Generic一样有效。通配符的界限是有效的,只要它们与类型参数的界限不明显,并且由于界限总是Object的子类型,? extends Object总是有效的。

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

1
interface NumberSupplier<N extends Number> extends Supplier<N> {}

我们可以写信

1
2
3
NumberSupplier<? extends Object> s1;
NumberSupplier<? extends Serializable> s2;
NumberSupplier<? extends BigInteger> s3;

甚至

1
NumberSupplier<? extends CharSequence> s4;

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

但不是

1
NumberSupplier<? extends String> s5;

由于StringNumber明显不同。

当涉及到任务时,我们可以使用问题中已经引用的子类型规则来得出结论:NumberSupplierNumberSupplier的一个子类型,因为? extends BigInteger包含? extends Object(也包含? extends Number),因为BigIntegerObjectNumber的一个子类型,但正如您正确指出的,这并不意味着OT适用于类型参数不是通配符的参数化类型。

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

Two reference types are the same compile-time type if they have the same binary name (§13.1) and their type arguments, if any, are the same, applying this definition recursively.

contains规则仍然有用,例如,它允许得出这样的结论:MapMap的子类型,因为对于第一个类型参数String <= String适用,第二个类型参数的类型参数由特定于通配符的contains规则覆盖。

所以剩下的问题是,哪个规则允许我们得出这样的结论:NumberSupplierNumberSupplierNumberSupplier是相同的类型,因此List>List>List>是可以相互转让的。

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

鉴于

1
interface MySupplier<S extends CharSequence&Appendable> extends Supplier<S> {}

以下声明显然有效:

1
2
List<MySupplier<? extends CharSequence>> list1 = Collections.emptyList();
List<MySupplier<? extends Appendable>>   list2 = Collections.emptyList();

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

javac认为他们不是

1
2
list1 = list2; // compiler error
list2 = list1; // dito

尽管任何涉及捕获转换的操作都会得出兼容的类型,例如

1
2
list1.set(0, list2.get(0)); // no problem
list2.set(0, list1.get(0)); // no problem

并且间接地做被拒绝的任务:

1
2
3
4
5
6
List<MySupplier<?>> list3;
list3 = list1;
list2 = list3; // no problem
// or
list3 = list2;
list1 = list3; // again no problem

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

1
2
3
4
5
6
List<MySupplier<? extends Object>> list4;
list4 = list1; // compiler error
list2 = list4; // dito
// or
list4 = list2; // dito
list1 = list4; // dito

但是,间接任务同样有效。

1
2
3
4
list4 = list3 = list1; // works
list1 = list3 = list4; // works
list4 = list3 = list2; // works
list2 = list3 = list4; // works

所以不管javac规则在这里使用什么,它都是不可传递的,它排除了子类型关系,以及一般的"它是同一类型"规则。看来,这确实是未指定的,并且直接影响到实现。而且,正如当前实现的那样,无边界的?是特殊的,允许使用任何其他通配符类型都不可能的分配链。