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

醉酒当歌 提交于 2021-02-17 08:55:34

问题


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?


回答1:


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.



来源:https://stackoverflow.com/questions/48170539/is-there-a-difference-when-specifying-upper-bounds-for-wildcards-explicitly

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!