显式指定通配符的上限时有区别吗? [英] Is there a difference when specifying upper bounds for wildcards explicitly?

查看:50
本文介绍了显式指定通配符的上限时有区别吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有一个通用的类Generic< A扩展BaseType> .

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

  Generic<?>泛型?扩展BaseType> 

嵌套通配符怎么办?

  List< Generic<?>>列出<通用< ;?扩展BaseType>> 


考虑到这一点,我认为它们是等效的. Generic 指定类型参数 A 的上限为 BaseType .

因此,无论我是否明确指定通配符,始终应自动"或隐式"以 BaseType 为边界.

下面,我尝试使自己的直觉与JLS保持一致.


我找不到有关隐式"范围的信息,因此我从查看子类型化规则开始.

阅读有关输入$ 4.10.2 ,它说:

给出通用类型声明 C< F1,...,Fn> (n> 0),这是参数化类型 C< C< T1,...,Tn>的直接超类型; 其中Ti(1≤i≤n)是一种类型,都是以下所有元素:

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

  • C< C1,...,Sn> ,其中Si含有Ti(1≤i≤n)(第4.5.1节).

(重点是我的)

据我了解,JLS中不将通配符"视为类型".因此,这不适用于前两个,但适用于两个 List 示例.

相反,这应该适用:

给出通用类型声明 C< F1,...,Fn> (n> 0),这是参数化类型 C< C< R1,...,Rn>的直接超类型; 其中Ri(1≤i≤n)中的至少一个是通配符类型参数,是参数化类型 C< X1,...的直接超类型.,Xn> 是将捕获转换应用于 C< R1,...,Rn> (捕获转换$ 5.1.10 改为 Generic<?> Generic< ;?扩展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]),其下限为空类型.

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

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

因此,似乎 Generic<?> Generic< ;?之间的子类型关系.扩展BaseType> 符合JLS的要求,这符合我的直觉.


对于嵌套通配符,我将使用:

据说一个类型实参T1包含另一个类型实参T2,如果由T2表示的类型集可证明是自反和反身下由T1表示的类型集的子集下列规则的传递式闭合(其中< ;:表示子类型)(§4.10))

结合

C< C1,...,Sn> ;,其中Si含有Ti(1≤i≤n)(第4.5.1节).

从上面我得到:

List< Generic<?> List< Generic< ;?如果 Generic<?> 包含 Generic< ;?扩展BaseType>>

尽管如此,我看不到如何使用contains规则.根据规则,我只能使用子类型化的其他信息.我已经知道子类型化在两种类型之间都是双向的.

尽管,如果答案是包含两者之间的子类型,我也可以证明 List< String> List< Object> 的子类型不应该.

此外,我需要显示形式为 Type< = OtherType 的东西,唯一具有右侧"type"形式的规则为 T< =T ,所以这些规则似乎根本没有帮助.

如何获取 List< Generic<?>> List< Generic< ;?扩展BaseType>> 是通过JLS彼此的子类型吗?

解决方案

从字面上回答您的第一个问题, Generic<?> Generic<之间是否存在显着差异"??扩展BaseType> ,答案必须是,它们不是等效的.

JLS§4.5.1 明确指出:

通配符?extend Object 等效于无界通配符?.

那么它等同于吗?仅在 BaseType Object 的情况下扩展BaseType ,但是即使那样,它们是等效的,但仍然存在显着差异,例如在没有捕获转换发生的地方:

 布尔值b1 =新的Object()instanceof Supplier<?> ;;//有效的代码布尔b2 =新的Object()instanceof Supplier< ;?扩展Object> ;;//无效的供应商<?> [] array1;//有效的声明供应商?扩展Object> [] array1;//无效的 

值得注意的是,与第一个直觉相反,给定声明 Generic< T扩展BaseType> ,并指定 Generic<?.扩展Object> 与等效的 Generic<?> 一样有效.只要通配符的边界与类型参数的边界不是可证明地不同,并且由于边界始终是 Object 的子类型,通配符的边界就有效.扩展对象始终有效.

因此,如果我们有类似类型的声明

 接口NumberSupplier< N扩展Number>扩展供应商< N>{} 

我们可以写

  NumberSupplier< ;?扩展Object>s1;NumberSupplier< ;?扩展Serializable>s2;NumberSupplier< ;?扩展BigInteger>s3; 

甚至

  NumberSupplier< ;?扩展CharSequence>s4; 

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

但不是

  NumberSupplier< ;?扩展String>s5; 

作为 String Number 可证明是不同的.

关于分配,我们可以使用问题中已经引用的子类型规则来得出 NumberSupplier< ;?扩展BigInteger> NumberSupplier< ;?的子类型?扩展Object> ,因为?扩展BigInteger 包含 ?扩展对象(以及包含 ?扩展Number ),因为 BigInteger Object 的子类型和 Number ,但是正如您正确指出的那样,这不适用于类型参数不是通配符的参数化类型.

因此,如果我们有类似 List< NumberSupplier<?>> 的声明,则 List< NumberSupplier< ;?扩展Object>> List< NumberSupplier< ;?扩展Number>> 并想根据§4.5.1的 contains 规则来推断其中一个是否是另一个的子类型,唯一适用的规则是当类型实参时是 same 类型( T< = T ),但是我们不需要子类型化规则,因为所有这些列表类型 javac 的实际行为相匹配,会产生一些非常有趣的结果:

给予

 接口MySupplier< S扩展了CharSequence& Appendable>扩展供应商{} 

以下声明显然是有效的:

  List< MySupplier< ;?扩展CharSequence>>list1 = Collections.emptyList();列表< MySupplier< ;?扩展Appendable>>list2 = Collections.emptyList(); 

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

但是 javac 认为它们不是

  list1 = list2;//编译器错误list2 = list1;//dito 

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

  list1.set(0,list2.get(0));//没问题list2.set(0,list1.get(0));//没问题 

并间接执行被拒绝的工作:

  List< MySupplier<?>>list3;list3 = list1;list2 = list3;//没问题//或者list3 = list2;list1 = list3;//再次没问题 

但是这里,?不等于?扩展对象:

  List< MySupplier< ;?扩展Object>>list4;list4 = list1;//编译器错误list2 = list4;//dito//或者list4 = list2;//ditolist1 = list4;//dito 

但是,间接分配仍然有效.

  list4 = list3 = list1;//有效list1 = list3 = list4;//有效list4 = list3 = list2;//有效list2 = list3 = list4;//有效 

因此,无论 javac 在这里使用什么规则,它都不是可传递的,它会排除子类型关系以及一般的相同类型"规则.看来,这确实是未(未)指定的,并且直接影响实现.而且,按照目前的实现,?是无边界的,这是特殊的,它允许使用其他任何通配符类型都无法实现的分配链.

Suppose I have a generic class Generic<A extends BaseType>.

Is there a notable difference, as far as the Java Language Specification is concerned, between the following two type declarations?

Generic<?>
Generic<? extends BaseType>

What about nested wildcards?

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


Thinking about this, I would assume these to be equivalent. Generic specifies that the type parameter Ahas BaseType for an upper bound.

Thus, the wildcard should always be "automatically" or "implicitly" bounded by BaseType, no matter whether I explicitly specify it or not.

Below, I try to reconcile my intution with the JLS.


I couldn't find information about "implicit" bounds, so I started by looking at subtyping rules.

Reading the JLS section about subtyping $4.10.2, it says:

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

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

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

(emphasis mine)

From what I understand, "wildcards" are not considered "types" in the JLS. So this can't apply to the first two, but it would apply to the two List examples.

Instead, this should apply:

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

(emphasis mine)

Applying capture conversion $5.1.10 to Generic<?> and Generic<? extends BaseType>; I think I get the same bounds on the fresh type variables. After capture conversion, I can use the "contains" rules to establish the subtyping.

For the first example, 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).

Since A1 is BaseType, the fresh variable has an upper bound of BaseType.

For the second case, via

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.

I get glb(BaseType, BaseType), which, again, is BaseType.

So it seems that the subtyping relationship between Generic<?> and Generic<? extends BaseType> goes both ways according to the JLS, which matches my intuition.


For the nested wildcards, I would use the "contains" rule:

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

Combined with

C<S1,...,Sn>, where Si contains Ti (1 ≤ i ≤ n) (§4.5.1).

from above, I get:

List<Generic<?>> is a direct supertype of List<Generic<? extends BaseType>> if Generic<?> contains Generic<? extends BaseType>>

Although, I don't see how I use the contains rule. The only additional information I can use is subtyping, according to the rules. I already know that subtyping goes both ways between the two types.

Although, if contains with subtyping between the two were the answer, I could also show that List<String> is a subtype of List<Object> which it isn't and shouldn't be.

Further, I need to show something of the form Type <= OtherType and the only rule with a right-hand-side of the form "type" is T <= T, so these rules don't seem to help at all.

How do I get that List<Generic<?>> and List<Generic<? extends BaseType>> are subtypes of one another through the JLS?

解决方案

Taking your initial question literally, whether "Is there a notable difference" between Generic<?> and Generic<? extends BaseType>, the answer must be, they are not equivalent.

JLS §4.5.1 clearly states:

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

So it would be equivalent to ? extends BaseType only if BaseType is Object, but even then, they are equivalent, but still bear notable differences, e.g. at places where no capture conversion happens:

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

It might be worth noting that contrary to the first intuition, given a declaration Generic<T extends BaseType>, specifying Generic<? extends Object> is as valid as the equivalent Generic<?>. The bounds to the wildcard are valid as long as they are not provably distinct to the type parameter’s bound and since the bounds are always subtypes of Object, ? extends Object is always valid.

So if we have a type declaration like

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

we can write

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

or even

NumberSupplier<? extends CharSequence> s4;

We can even implement it without an actual type extending Number and CharSequence using () -> null

but not

NumberSupplier<? extends String> s5;

as String and Number are provably distinct.

When it comes to assignments, we can use the subtyping rules already cited in the question to conclude that NumberSupplier<? extends BigInteger> is a subtype of NumberSupplier<? extends Object>, because ? extends BigInteger contains ? extends Object (and also contains ? extends Number), because BigInteger is a subtype of Object and Number, but as you correctly noted, this does not apply to parameterized types whose type arguments are not wildcards.

So if we have declarations like List<NumberSupplier<?>>, List<NumberSupplier<? extends Object>>, or List<NumberSupplier<? extends Number>> and want to reason whether either is a subtype of the others according to §4.5.1’s contains rule, the only rule that could apply, is, when the type arguments are the same type (T <= T), but then, we wouldn’t need subtyping rules, as then, all these list types are the same type:

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.

The contains rule still is useful, e.g. it allows to conclude that Map<String,? extends Number> is a subtype of Map<String,Integer>, because for the first type argument String <= String applies and the type arguments to the second type parameter are covered by a wildcard specific contains rule.


So the remaining question is, which rule allows us to conclude that NumberSupplier<?>, NumberSupplier<? extends Object>, or NumberSupplier<? extends Number> are the same types, so that List<NumberSupplier<?>>, List<NumberSupplier<? extends Object>>, or List<NumberSupplier<? extends Number>> are assignable to each other.

It doesn’t seem to be capture conversion, as capture conversion would imply calculating effective bounds, but also create a "fresh type variable" for each wildcard that is definitely a different type. But there is no other rule covering wildcard compatibility. Or I didn’t find it. Trying to match the specification with the actual behavior of javac had some very interesting results:

Given

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

The following declarations are obviously valid:

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

Since in both cases, the wildcard’s bound is redundant as matching one of S’s bound, we might guess that they are actually the same type.

But javac thinks they are not

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

though any operation involving capture conversion does conclude compatible types, e.g.

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

and doing the rejected assignments indirectly works:

List<MySupplier<?>> list3;
list3 = list1;
list2 = list3; // no problem
// or
list3 = list2;
list1 = list3; // again no problem

but here, ? is not equivalent to ? extends Object:

List<MySupplier<? extends Object>> list4;
list4 = list1; // compiler error
list2 = list4; // dito
// or
list4 = list2; // dito
list1 = list4; // dito

but again, indirect assignments work.

list4 = list3 = list1; // works
list1 = list3 = list4; // works
list4 = list3 = list2; // works
list2 = list3 = list4; // works

So whatever rule javac uses here, it’s not transitive, which rules out subtype relationships, as well as a general "it’s the same type" rule. It seems, that this is truly un(der)specified, and directly affects the implementation. And, as currently implemented, ? without bounds is special, allowing an assignment chain that is not possible with any other wildcard type.

这篇关于显式指定通配符的上限时有区别吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆