This Java tutorial on generics says:
Wildcards also have the advantage that they can be used outside of method signatures, as the types of fields, local variables and arrays. Here is an example.
Returning to our shape drawing problem, suppose we want to keep a history of drawing requests. We can maintain the history in a static variable inside class Shape, and have drawAll() store its incoming argument into the history field.
static List<List<? extends Shape>> \\ 1 history = new ArrayList<List<? extends Shape>>(); \\ 2 public void drawAll(List<? extends Shape> shapes) { \\ 3 history.addLast(shapes); \\ 4 for (Shape s: shapes) { \\ 5 s.draw(this); \\ 6 } }
(I added the line numbers in the code snippet.)
There are three issues that baffle me about the code snippet.
In line 2 the wildcard symbol is used to instantiate an object. What is the object's type? In all the examples presented thus far in the tutorial, the wildcard symbol was only used as a formal type parameter, not as an actual type argument. Actual type arguments have always been concrete classes, whose class definition you can read in some source file.
In line 4 the history
field is modified via its add
method. However a previous page of the same tutorial says that it is illegal to modify objects via variables declared with an upper-bounded wildcard type e.g. List<? extends Shape> shapes
.
The use of a wildcard in line 1 in the declaration of a static variable seems to be at odds with the following words from a later page of the same tutorial:
That is why it is illegal to refer to the type parameters of a type declaration in a static method or initializer, or in the declaration or initializer of a static variable.
ArrayList
. The generics here are not used to 'make a new ?
' - that cannot be done. You're simply making a new arraylist here, is all. That arraylist is then parameterized - it is bound to contain solely things whose type signature matches List<? extends Shape>
. Because that's what is inside the outer <>
. Making a new arraylist does not instantly fill it with a bunch of data. An ArrayList is created empty, and therefore, there is no need to make anything of the type in the outer <>
. Some code will perhaps add some lists to this arraylist later on. If it's a List<Shape>
, that's fine. If it's a List<Rectangle>
, that is also fine - both are compatible with the List<? extends Shape>
signature.
In line 4 the history field is modified via its add method.
That's a confusing, bordering on incorrect, way to say this. The history
field is not modified at all; to modify it, you'd have to write history = something
. No, the history
field is referencing some object (it's like a page in an address book, it's not like a house) - history.add
will dereference this reference (walk over to the actual house by going to the address - we aren't modifying the address book at all), and then we change the house.
says that it is illegal to modify objects via variables
This is an oversimplification that does not hold in all cases. It does here, but, the trick is, this arraylist is not declared via ? extends
. Just look at it: It's declared as List<List<X>>
. The component type (the type of the objects this list is going to contain) has no extends
at all. Sure, the X
does, but that's one layer deeper, and this oversimplified rule refers only to the immediate component (the thing inside the outer <>
).
It's oversimplified because it actually applies PECS. Essentially, given:
ArrayList
/List
here) declares a type var E
.? extends
wildcard (and note that just ?
is short for ? extends Object
, so is also an extends
wildcard for the purposes of this logic).E
(Other than literally null
, but that's not very useful).That's because the actual E
, which we don't know here because you declared it with a ? extends
bound, could be Shape, or Square, or Circle. We just don't know. There is no java expression that has the property that it can legally be passed as method parameter where the parameter type is something we don't know, all we know is - it is Square, or Shape, or Circle.
Contrast with a ? super Square
bound: Here we don't know what the actual type is either (still using ?
here, so we don't know), but we do know it is Square
, or Shape
, or Object
, and it can't be anything else. new Square()
is valid here. Imagine these three methods:
void foo(Object o) {}
void foo(Shape o) {}
void foo(Square o) {}
new Square()
is fine for any and all of them. Therefore, it's fine for void foo(? super Square foo) {}
. That is itself not legal java, but void foo(E foo) {}
is, and if E
is bound as ? super Square
- now you know why you can call .add(x)
on a List<? super TypeOfX>
whereas you can't call .add()
at all on a List<? extends Type>
. (except with literally null
which fits all types, but that's an academic curiosity, not a useful thing to do).
Point is, we aren't calling .add
on a List
whose generics type is ? extends
. We are calling .add
on a List
whose generics type is List<X>
, where, sure, X
does contain extends
but that's not the relevant place to look.
3.
<>
syntax is used both to declare variables as well as using them.
Given:
String x;
we aren't using x
, we are declaring: "I shall now declare there is a variable, named x
, which is constrained: It shall ever only point at nothing (null
), or, objects that are either instances of String
or some subtype thereof (which for string trivially is just 'instances of String', as String is final
so no subtypes can exist)".
Whereas with System.out.println(x)
we aren't declaring a new variable named x, we are using it.
The same applies to generics, but it's more confusing in the sense that declaration and use looks highly similar.
There are 2 ways to create a type var:
class Foo<T> {} // this DECLARES a new type var, named 'T'
public <T> void foo() // This DECLARES a new type var, named 'T'
All other places is usage of it. The statement:
type parameters of a type declaration
Is referring to the T
in class Foo<T> {}
. You can't use those in a static anything.
static List<List<? extends Shape>> \\ 1 history = new ArrayList<List<? extends Shape>>();
There are no type variables used here AT ALL. A type variable is something like T
. There aren't any in this statement. <? extends Shape>
is not a 'type variable'. It's a type bound. Type variables are, as stated above, The T
in class Foo<T>{}
or the T
in <T> returnType methodName() {}
. By convention they are single capital letters.