Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the formal conditions for a wildcard parameter in a Java generic type to be within its bounds?

Tags:

java

generics

jls

With parameterized types in Java, how do the rules that check if a parameter is within its bound work exactly for wildcards?

Given a class like this:

class Foo<T extends Number> {}

Experimenting with what the compiler accepts learns that:

  • A ? extends wildcard using an unrelated interface type is allowed: Foo<? extends Runnable> is valid
  • A ? extends wildcard using an unrelated class type is not allowed: Foo<? extends Thread> is invalid. That makes sense because no type can be a subtype of both Number and Thread
  • In a ? super wildcard, the lower bound in the wildcard must be subtype of the bound of the type variable: Foo<? super Runnable> is not allowed because Runnable is not a subtype of Number. Again, this restriction makes perfect sense.

But where are these rules defined? Looking at the Java Language Specification section 4.5, I don't see anything distinguishing interfaces from classes; and when applying my interpretation of the JLS Foo<? super Runnable> is said to be valid. So I probably misunderstood something. Here's my attempt:

From that section of the JLS:

A parameterized type consists of a class or interface name C and an actual type argument list <T1 , ... , Tn>. It is a compile time error if C is not the name of a generic class or interface, or if the number of type arguments in the actual type argument list differs from the number of declared type parameters of C. In the following, whenever we speak of a class or interface type, we include the generic version as well, unless explicitly excluded. Throughout this section, let A1 , ... , An be the formal type parameters of C, and let be Bi be the declared bound of Ai. The notation [Ai := Ti] denotes substitution of the type variable Ai with the type Ti, for 1 <= i <= n, and is used throughout this specification.

Let P = G<T1, ..., Tn> be a parameterized type. It must be the case that, after P is subjected to capture conversion (§5.1.10) resulting in the type G<X1, ..., Xn>, for each actual type argument Xi, 1 <= i <= n , Xi <: Bi[A1 := X1, ..., An := Xn] (§4.10), or a compile time error occurs.

Apply that to P = Foo<? super Runnable>: that gives C = Foo, n = 1, T1 = ? super Runnable and B1 = Number.

For capture conversion this part of the definition of capture conversion applies:

If Ti is a wildcard type argument of the form ? super Bi, then Si is a fresh type variable whose upper bound is Ui[A1 := S1, ..., An := Sn] and whose lower bound is Bi.

That gives G<X1, ..., Xn> = Foo<X> where X is a fresh type variable with upper bound Number and lower bound Runnable. I don't see anything explicitly forbidding such a type variable.

There are no type variables in B1 = Number, so Bi[A1 := X1, ..., An := Xn] is still simply Number. X has Number as upper bound (coming from the capture conversion), and according to the subtyping rules "The direct supertypes of a type variable are the types listed in its bound", so X <: Number (= Bi[A1 := X1, ..., An := Xn]), so this parameter is within its bounds. (But it isn't!)

Following the same reasoning every wildcard is within its bounds, so something here isn't right... But where exactly did this reasoning go wrong? How do these rules work when applied correctly?

like image 970
Wouter Coekaerts Avatar asked Aug 09 '11 21:08

Wouter Coekaerts


People also ask

What are wildcards in generics in Java?

The question mark (?) is known as the wildcard in generic programming. It represents an unknown type. The wildcard can be used in a variety of situations such as the type of a parameter, field, or local variable; sometimes as a return type.

How do you declare a generic bounded type parameter in Java?

To declare a bounded type parameter, list the type parameter's name, followed by the extends keyword, followed by its upper bound, which in this example is Number . Note that, in this context, extends is used in a general sense to mean either "extends" (as in classes) or "implements" (as in interfaces).

What is the difference between a wildcard bound and a type parameter bound?

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 bounded and unbounded wildcards in generics Java?

both bounded and unbounded wildcards provide a lot of flexibility on API design especially because Generics is not covariant and List<String> can not be used in place of List<Object>. Bounded wildcards allow you to write methods that can operate on Collection of Type as well as Collection of Type subclasses.


1 Answers

JLS on generics is incomplete, and you caught another hole in it. Lower bound on type variables is barely discussed, and I don't see any restriction in spec either on X having upper bound Number and lower bound Runnable. They probably left it out.

Intuitively, there must be at least one possible type that satisfies both upper bound and lower bound of a type variable, otherwise the variable and all types using the variable would be useless. Since this is almost certainly a programming mistake, compile should fail.

It's easy to check whether upper bound and lower bound make an empty set of types. All super types of the lower bound are known; at least one of them should be the upper bound, otherwise there is no type that's within both bounds.

--

The two Foo<? extends A> cases are well defined in the spec. With capture conversion, we have new type variable X with upper bound A & Number, and the spec says for an upper bound V1&...&Vm

It is a compile-time error if for any two classes (not interfaces) Vi and Vj,Vi is not a subclass of Vj or vice versa.

Therefore if A=Thread, capture conversion fails.

like image 71
irreputable Avatar answered Sep 18 '22 00:09

irreputable