Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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?

like image 515
phant0m Avatar asked Jan 09 '18 14:01

phant0m


People also ask

What is the difference between upper bounded wildcards and bounded type parameters?

What is the difference between a wildcard bound and a type parameter bound? A wildcard can have only one bound, while a type parameter can have several bounds. A wildcard can have a lower or an upper bound, while there is no such thing as a lower bound for a type parameter.

What is upper bounded wildcard?

A bounded wildcard is one with either an upper or a lower inheritance constraint. The bound of a wildcard can be either a class type, interface type, array type, or type variable. Upper bounds are expressed using the extends keyword and lower bounds using the super keyword.

When would you choose to use a lower bounded type vs an upper bounded type?

Upper bound wildcard − If a variable is of in category, use extends keyword with wildcard. Lower bound wildcard − If a variable is of out category, use super keyword with wildcard. Unbounded wildcard − If a variable can be accessed using Object class method then use an unbound wildcard.

How we can define lower bound wildcard?

A lower bounded wildcard is expressed using the wildcard character ('? '), following by the super keyword, followed by its lower bound: <? super A>. Note: You can specify an upper bound for a wildcard, or you can specify a lower bound, but you cannot specify both.


1 Answers

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.

like image 150
Holger Avatar answered Sep 28 '22 00:09

Holger