22

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?

phant0m
  • 16,595
  • 5
  • 50
  • 82
  • Cannot explain the reason for this but you can add raw typed ```Generic``` values to ```List>``` without complains but if you attempt to add it to a ```List>``` where ```Generic``` the compiler outputs a warning (javac 1.8.0_112). – Valentin Ruano Jan 09 '18 at 17:58
  • 1
    It took me a while to realize that you actually have two questions here. The first one is basically self-answered already, after capture conversion, `List>` becomes `List>` and `List>` becomes `List>`, so the form doesn’t matter for most operations, but the other question is, which formal rule(s) allow to conclude that either is a subtype of the other, which would even be the same question when literally assigning `List>` to `List>`. – Holger Jan 17 '18 at 18:37
  • @Holger In a way, yes. The subtyping "question" is how I wanted to answer my "first"/original question. But maybe it can also be answered without subtyping and capture conversion etc. Did you mean to use my second example when writing this? From my interpretation of the JLS, capture conversion doesn't apply at all for these nested wildcards. – phant0m Jan 17 '18 at 23:07
  • 4
    Great question. Took me some days. Even after searching that long, it’s hard to believe that there’s something missing in the specification, especially for a construct that doesn’t seem to be so obscure. – Holger Jan 19 '18 at 18:21

1 Answers1

9

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • As of why list1 = list2 gives compilation error. According to https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.5.1 Two type arguments are provably distinct if one of the following is true: Each type argument is a type variable or wildcard, with upper bounds (from capture conversion, if necessary) of S and T; and neither |S| <: |T| nor |T| <: |S|. And: https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.6 The erasure of a type variable (§4.4) is the erasure of its leftmost bound. – Klaimmore Jan 22 '18 at 21:10
  • 1
    @Klaimmore but there is no clear statement about whether and when the “provably distinct” rule should apply in assignments. You know, you *can* assign a `MySupplier extends CharSequence>` to a `MySupplier extends Appendable>` and vice versa, which doesn’t contradict your cited rules, as it refers to the bounds “from capture conversion, if necessary”. There is no indicator that the “provably distinct” rule has been used by the compiler for the list assignments, especially, as it wouldn’t explain why `List>` is not assignable. – Holger Jan 23 '18 at 08:02
  • *"boolean b2 = new Object() instanceof Supplier extends Object>;"* ... my understanding that this statement fails irrespective of any nuanced subtype analysis simply because the `instanceof` operator requires a *reifiable type*, and the only wildcard type that is reifiable is `>` (the semantic equivalence is granted). – scottb Feb 11 '21 at 18:50
  • 1
    @scottb is this a contradiction? The specification says “`? extends Object` is equivalent to the unbounded wildcard `?`”, but it is not. That’s the whole point. If it was truly and fully equivalent, `Supplier extends Object>` was treated as a reifiable type. – Holger Feb 12 '21 at 08:00