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

时间:2018-01-09 14:38:08

标签: java generics language-lawyer wildcard jls

假设我有一个通用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>>成为彼此的子类型?

1 个答案:

答案 0 :(得分:6)

从字面上看你的初步问题,Generic<?>Generic<? extends BaseType>之间是否存在“显着差异”,答案必须是,它们并不等同。

JLS §4.5.1明确指出:

  

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

仅当? extends BaseTypeBaseType时,它才相当于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;

StringNumber 可证明不同

说到分配时,我们可以使用问题中已经引用的子类型规则得出结论:NumberSupplier<? extends BigInteger>NumberSupplier<? extends Object>的子类型,因为? extends BigInteger 包含 ? extends Object(以及包含 ? extends Number),因为BigIntegerObjectNumber的子类型,但是当你正确指出,这不适用于类型参数不是通配符的参数化类型。

因此,如果我们有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无边界是特殊的,允许使用任何其他通配符类型无法实现的赋值链。