CSE 331: Generics
Course motto: Your job is to deliver code that you know works.
A List<Integer> has been in this course's programs since
Lecture 1, and Lecture 16 kept its durations in a
Set<Duration>. No lecture has said yet how the angle brackets
work, or who checks them. A type such as List is written once,
for elements of a type its author never chose, and code that uses
it writes the type in the brackets. What is written there is a
claim about every element, and javac checks it. So this lecture
asks two questions. How do we write such a type ourselves? And
what happens when the brackets meet Lecture 14's subtypes?
javac accepts a SaleProduct where a Product is expected. Does
it accept a List<SaleProduct> where a List<Product> is
expected? The answer to that question comes from a definition in
Lecture 14. Most of the rest of the lecture is about writing
types that say what we mean.
A queue of anything
Recall the queue for office hours, HelpQueue, and
its first implementation, ListQueue, which keeps
the students in one list. A student is named by a NetID, and a
NetID is a String, so both types say String throughout: join
takes a String, next returns one, and the list inside is
a List<String>.
Now suppose a second client, a bakery, wants a queue of ticket
numbers. The code of ListQueue would serve the bakery, since it
calls no method that only a String has. But the types in
ListQueue say String.
With what we know so far, there are two ways to reuse the code.
The first is to copy the class and change String to Integer in
the copy. Lecture 14 counted the cost of a
copy: every later change has to be made in every
copy, and nothing warns us about the copy we forgot. The second
way is the one Java programs had to use before 2004.1
Write the class once, for Objects. Every class is a Java subtype
of Object, so a queue of Objects accepts an element of any class.
Here is that queue. It is ListQueue with Object written
wherever String was:
| ObjectQueue.java | |
|---|---|
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | |
And here is a client, an office hours program like Lecture 10's. Its queue is meant to hold NetIDs, and its first comment says so:
| ObjectQueue.java | |
|---|---|
20 21 22 23 24 25 26 27 28 29 30 31 32 | |
On line 29, next returns an Object, and main wants a String.
The (String) in front of the call is a cast: it tells
javac to treat the Object as a String. An Object might be a
String, so javac accepts the cast. The check is made when the
program runs: if the object is not a String, the cast throws
a ClassCastException.
One of the three calls of join is a mistake. Find it. Then
write down what you expect javac to say, and what you expect
the program to print. Here is the run:
$ javac ObjectQueue.java
$ java ObjectQueue
now helping ada
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
at ObjectQueue.main(ObjectQueue.java:29)
javac accepted every line. ada was helped. Then the program
stopped at line 29, at the cast. The second element of the queue
is the student number that line 24 joined, and a number is not a
String. (Java wraps the int 1234567 in an object of the class
Integer to pass it where an Object is expected. So the message
says Integer.)
The mistake and the exception are on different lines. The
mistake is on line 24, where a number joined a queue of NetIDs.
The exception is thrown five lines later, on line 29, where the
number came out. In a larger program, the call of join can be
in one file and the cast in another. If the program read its
NetIDs as input, it would fail only on a run on which somebody
typed a number.
The comment on line 21 says Every element is a NetID. That is a
claim about every element of the queue, on every run. Who checks
it? Lecture 1 said that a tool can confirm a claim about every
run, but only the one claim it was built to check, and
its example of a tool was javac. Here is the claim that javac
was built to check:
On every run, a variable refers only to objects of its declared type: objects whose class is that type or a Java subtype of it.
A parameter and a field are variables too, and the claim covers
what a method returns in the same way. (A variable may also be
null.) A language that keeps this claim is called type safe,
and these notes call the claim type safety.2
javac confirms type safety one line at a time. Each place that
takes an object expects a type: a variable that is stored into, a
parameter, a method's return. javac accepts an expression there
with no warning only if its type is the expected type or a Java
subtype of it. (A number such as an int follows
other rules: Java wraps it in an object where an object is
expected, as on line 24.) So javac refuses a line that could
break the claim, with two exceptions that this lecture shows. For
those two, Java makes the check when the program runs. (The last
section mentions a third kind of line, which javac accepts with
a warning.)
In ObjectQueue.java, type safety holds, and it says nothing about
NetIDs. The parameter of join is declared an Object, and so is
each element of the list inside. A number is an Object, so
javac accepts line 24. All that javac confirms about an
element of this queue is that it is an Object.
The cast is the first of the two exceptions. On line 29, next
returns an Object, and netid is declared a String. Without the
cast, javac refuses the line: an Object need not be a String.
The cast says: this Object is a String, trust me. javac cannot
confirm that. So Java checks it when the program runs, and throws
the exception before anything that is not a String reaches
netid. Type safety still holds. But the check that an element
is a String is now made one element at a time, and only on the
runs that reach the cast. A check made one run at a time has the
limit that Lecture 1 found in testing: it can refute a claim
about every run and never confirm it.
So we want javac to check that every element is a String. For
that, the claim has to be written where javac reads: in the
queue's type.
A class with a type parameter
Java lets a class leave a type open, for each client to fill in.
Here is the queue written that way, as an interface and a class.
(Every program in this lecture is in the course's public
repo, to download and run. A copy that javac refuses
is kept there as a .txt file, and its opening comment says how
to compile it.)
| GenericQueue.java | |
|---|---|
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | |
Start with line 43. The <E> after the name Queue declares a
type parameter named E. Inside the interface, E stands
for a type that we, the authors, do not know: the type of this
queue's elements. So join takes an E, next returns an E,
and waiting returns a List<E>.
A type parameter works like a method's parameter. A method's parameter stands for a value that the method's author does not know, and each call supplies the value. A type parameter stands for a type that the class's author does not know, and each use of the class supplies the type.
By convention a type parameter's name is one capital letter: E
for an element, K and V for the key and the value of a map,
and T for any type.3
Now line 68. ListQueue<E> declares a type parameter too, and
then uses it three times before its first method: it implements
Queue<E>, it keeps a List<E>, and it makes an ArrayList<E>.
From there on, the class is Lecture 10's ListQueue with E
written wherever String was, and with its parameter netid
renamed e.
Definition: generic class, type parameter, type argument
A generic class or interface declares one or more type
parameters in angle brackets after its name, as in
class ListQueue<E>. Inside the class, a type parameter
stands for a type that is chosen wherever the class is used.
Code that uses the class chooses by writing a type
argument in the brackets, as in ListQueue<String>.
Java programmers call this feature generics.
java.util.List is a generic interface like our Queue. It is
declared as interface List<E>, in a file of Java like any
other, and List<String> is that interface with String as its
type argument.4 javac checks a List<String> by
the same rules as our Queue<String>.
Here is the client again, with the queue's type filled in:
| GenericQueue.java | |
|---|---|
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | |
Line 20 declares queue a Queue<String>, so for this variable
E is String. join takes a String. next returns a String,
and line 26 stores it with no cast. Line 31 uses the same class
for the bakery, with Integer as the type argument. Line 34 gets a
number back, again with no cast. Here is the run:
$ javac GenericQueue.java
$ java GenericQueue
now helping ada
now helping eve
now serving ticket 41
Now make the mistake again. Suppose a copy of the program adds
queue.join(1234567); as line 22, between the two students. What
does javac say to it?
$ javac GenericQueue.java
GenericQueue.java:22: error: incompatible types: int cannot be converted to String
queue.join(1234567);
^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
javac refuses the program, and it points at the line with the
mistake. (The note in the message is javac's, and we can ignore
it.) Since javac refuses the program, the program never runs,
and no number reaches the queue.
The comment still says Every element is a NetID. But now the
type of queue says that every element is a String, where
javac reads it. For a Queue<String>, the parameter of join
is a String, and next returns a String. So type safety, the
claim that javac already confirms, now covers each element on
its way into the queue and on its way out. javac checks every
call of join on a Queue<String> against that claim. So no
line of this program joins anything but a String, on any run.
And javac knows that what next returns is a String, with
nobody's word to take for it. That is why line 26 needs no cast.
No primitive types. The queue of tickets is a
Queue<Integer>, not a Queue<int>, since javac refuses int
as a type argument. A copy that writes int in both places on
line 31 does not compile:
$ javac GenericQueue.java
GenericQueue.java:31: error: unexpected type
Queue<int> tickets = new ListQueue<int>();
^
required: reference
found: int
GenericQueue.java:31: error: unexpected type
Queue<int> tickets = new ListQueue<int>();
^
required: reference
found: int
2 errors
A type argument has to be a reference type: a class, an
interface, an array type, or a type parameter such as E. int
is none of these. So we write Integer, the class whose objects
each hold one int. Java converts between the two as needed.
Line 32 passes the int 41 where an Integer is expected, and
line 34 stores the Integer that next returns in an int.
The queue's code stores an E and hands it back. What else may
the code do with one?
What the code may do with an E
The code of ListQueue<E> does not know what E is: String for
one client, Integer for the next. So javac lets the code do
with an E only what is right for every type argument. Since
every type argument is a reference type, an E that is not
null refers to an object. The code may therefore call on an E
the methods that every object has, the public methods of Object,
and no others.
On lines 76 and 77, join does two things with e. It calls
queue.contains(e), which compares e with each element by
equals. And it writes e + " is in the queue", which calls
toString on e. Both are methods of Object, so every E has
them. (This is one more reason to keep Lecture 16's rule for
equals. A queue of Durations decides whether a
Duration is already in the queue by calling the equals that
Duration wrote.)
Often the code needs more than Object has. Here is a first try at a method for Lecture 14's store, which finds the cheapest product in a list. It is meant to work on a list of any kind of product, and to hand back a product of the list's own element type. (The repo keeps this first try as CheapestUnbounded.txt.)
| Cheapest.java | |
|---|---|
34 35 36 37 38 39 40 41 42 43 44 45 46 | |
One thing on line 38 is new. The <P> before the return type
declares a type parameter that belongs to this one method. A
method that declares a type parameter is a generic method.
Its type parameter P may be used in the types of the method's
parameters, in its return type, and in its body. Here cheapest
takes a List<P> and returns a P.
Is the first try right? Decide which lines javac refuses:
$ javac Cheapest.java
Cheapest.java:41: error: cannot find symbol
if (p.getPrice() < best.getPrice()) {
^
symbol: method getPrice()
location: variable p of type P
where P is a type-variable:
P extends Object declared in method <P>cheapest(List<P>)
Cheapest.java:41: error: cannot find symbol
if (p.getPrice() < best.getPrice()) {
^
symbol: method getPrice()
location: variable best of type P
where P is a type-variable:
P extends Object declared in method <P>cheapest(List<P>)
2 errors
Read the last line of the first message: P extends Object. The
rule of this section holds for a method's type parameter too. The
body may do with a P only what is right for every type, and
Object has no getPrice. So javac refuses both calls of
getPrice on line 41.
The method has to say that it is for products only. Here is line 38 as Cheapest.java has it:
| Cheapest.java | |
|---|---|
38 | |
Here extends Product is a bound on P. The client may choose
only Product, or a Java subtype of Product, for P. Since an
object of any such type is a Product, the body may use a P as a
Product. So line 41 may call getPrice, and javac accepts
the method.
Definition: bound
A type parameter declared <P extends A> has the bound
A. A type argument for P must be A or a Java subtype of A.
The code in the scope of P may use a P as an A.
A type parameter declared with no bound, as in <E>, has the
bound Object.
A bound works like a precondition, written about a type in place
of a value. javac checks it at every call of a generic method,
and wherever a type argument is written for a generic class. (The
word is extends even when A is an interface.) Here are two
calls of cheapest, on two lists with different element types:
| Cheapest.java | |
|---|---|
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |
cart is Lecture 14's cart, a List<Product> that holds a
Product and a SaleProduct. (The two classes are the ones in that
lecture's ProductsFixed.java, whose toString calls getPrice,
so a sale product prints at its sale price.) deals is new: a
List<SaleProduct>, whose type says that it holds sale products
only. Neither call of cheapest writes a type argument. javac
works one out from the argument of each call:
cheapest(cart).cartis aList<Product>, soPis Product, and the call returns a Product.cheapest(deals).dealsis aList<SaleProduct>, soPis SaleProduct, and the call returns a SaleProduct. Line 30 stores it in a variable declared a SaleProduct, with no cast.
Here is the run:
$ javac Cheapest.java
$ java Cheapest
cheapest(cart): m&ms: $1.0
cheapest(deals): gum: $0.5
Why not write cheapest as Lecture 14 wrote total, to take a
List<Product> and return a Product? Then the call on line 30
would hand back a Product, and line 30 would need a cast to
store it as a SaleProduct. The next section finds a
second reason.
A bound you will meet often is an interface of the JDK's,
Comparable<T>, for types whose values have an order. It
declares one method, compareTo, which takes a T. You may have
written implements Comparable<Book> on a class in an earlier
course: a Book can be compared with another Book. String
implements Comparable<String>, and Integer implements
Comparable<Integer>.5 A method that compares its
elements can declare <T extends Comparable<T>>. The bound
mentions T itself, which is allowed. Read it as: a T can be
compared with another T. Exercise 2 writes such a method.
A list of sale products is not a list of products
Now for the question we started with. cheapest accepted the
cart and the deals, since its P can be either element type.
Lecture 14's total declares no type parameter. It
takes a List<Product>. Here it is as that lecture wrote it, in
CartsExact.txt, a copy of this lecture's Carts.java that is
compiled under that name:
| Carts.java | |
|---|---|
38 39 40 41 42 43 44 45 | |
In Lecture 14, main put a SaleProduct into a cart, and javac
accepted the SaleProduct where a Product was expected. This time
main makes the same cart and deals as Cheapest.java's main,
and hands total each of the two lists, on lines 29 and 30:
| Carts.java | |
|---|---|
29 30 | |
Line 29 is Lecture 14's call. Line 30 hands total the deals,
a List<SaleProduct>, where a List<Product> is expected. Every
SaleProduct is a Product. So is a List<SaleProduct> accepted
where a List<Product> is expected? Decide before you read on.
We do not have to guess. Lecture 14 defined a Java subtype by what the compiler does: B is a Java subtype of A if the compiler accepts a B where an A is expected. So to find out whether one type is a Java subtype of another, ask the compiler. Write a method that takes an A, hand it a B, and compile. CartsExact.txt is exactly that program:
$ javac Carts.java
Carts.java:30: error: incompatible types: List<SaleProduct> cannot be converted to List<Product>
System.out.println("total(deals): $" + total(deals));
^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
javac refuses line 30. A List<SaleProduct> is not a Java
subtype of List<Product>.
The is-a test said yes: in plain English, every list of sale products is a list of products. That test has misled us before, about a square and a rectangle. The way to settle the question is the same as it was there: ask what the supertype promises.
Lecture 14 compared two types method by method. A behavioral subtype needs, for each method, a precondition no stronger and a postcondition no weaker. That lecture also read part of each off a method's types: what a method returns belongs to its postcondition, and a wider parameter type is a weaker precondition. So let's compare two methods of the two lists by their types:6
get. AList<SaleProduct>'sgetreturns a SaleProduct, where aList<Product>'s returns a Product. Every SaleProduct is a Product, so the postcondition ofgetis no weaker.add. In aList<Product>,addtakes a Product, so a client may pass it any Product. In aList<SaleProduct>,addtakes a SaleProduct, so a client may not pass it a plain Product. The parameter type is narrower, so the precondition ofaddis stronger.
A stronger precondition is what the definition rules out. So a
List<SaleProduct> is not a behavioral subtype of
List<Product>, and the rule from that lecture says not to
declare it a Java subtype. Java follows that rule here: a
List<SaleProduct> is not a Java subtype of List<Product>, as
the refusal of line 30 showed.
But total never calls add. It only reads. So why does
javac refuse a call that would have worked? javac checks a
call against the type of the method's parameter. It does not
look at the method's body. A client reasons the same way, from
the specification alone. The type List<Product>
lets the body of total add any Product to its cart. Suppose the
store starts to charge ten cents for a bag. A later total could
then begin with the line cart.add(new Product("bag", 0.1));.
Its specification would gain @modifies cart, but its
parameter's type would not change. Handed the deals, that line
would put a plain Product into a list whose type
says SaleProduct.
Here is the same mistake in a program with no method in the way.
(The repo keeps it as ListCarts.txt, since javac refuses it.)
| ListCarts.java | |
|---|---|
20 21 22 23 24 25 26 27 28 29 30 31 | |
Read the three steps as if javac accepted them all:
- Line 26 gives the list of deals a second name,
all, declared aList<Product>. No list is copied.allanddealsare two names for one list. - Line 27 adds the skittles, a plain Product, through
all. The type ofallallows that. The skittles are now in the one list, so they are indeals. - Line 29 reads the second element through
deals, whose type says the element is a SaleProduct. The element is the skittles, and the skittles are not a SaleProduct.
Lines 27 and 29 each do what the type of their variable allows.
So the line to refuse is line 26, and javac refuses it:
$ javac ListCarts.java
ListCarts.java:26: error: incompatible types: List<SaleProduct> cannot be converted to List<Product>
List<Product> all = deals;
^
1 error
Rule: the type arguments must be the same
For a generic class or interface C, a C<B> is accepted
where a C<A> is expected only if A and B are the same type.
It is not enough for B to be a Java subtype of A.
So a List<SaleProduct> is not accepted where a
List<Product> is expected.
Programmers call this property invariance: Java's generic types are invariant in their type arguments.
The rule is about the type arguments inside the brackets. The
class or interface outside them still follows Lecture 14's chain
of extends and implements. ArrayList<E> is declared to
implement List<E>, so an ArrayList<Product> is a Java subtype
of List<Product>. Cheapest.java and Carts.java each store an
ArrayList<Product> in a variable declared
a List<Product>.7
Arrays took the other answer
Java has had arrays since its first version, years before it had
type parameters. For arrays, Java gave the other answer:
SaleProduct[] is a Java subtype of Product[].8 So
the same three steps, written with arrays, compile:
| ArrayCarts.java | |
|---|---|
15 16 17 18 19 20 21 22 23 24 25 26 | |
javac accepts line 21 this time. So what happens to the
skittles on line 22? Predict the run:
$ javac ArrayCarts.java
$ java ArrayCarts
Exception in thread "main" java.lang.ArrayStoreException: Product
at ArrayCarts.main(ArrayCarts.java:22)
The program stopped at line 22, the store, with an
ArrayStoreException. Each array keeps, while the program runs,
the class it was made to hold. Each time a program stores an
object in an array, Java checks the object against that class,
when the program runs. The array that deals and all name was
made to hold sale products, and the skittles are not one.
So the skittles never reached deals. But compare when the
mistake is found in each case. With lists, javac refused the
second name, so the program never ran. With arrays, javac
accepted every line, and the program failed on the run that made
the bad store. As far as javac checks, a Product[] takes any
Product in a store, and a SaleProduct[] cannot take a plain
Product. So by its types, SaleProduct[] is a Java subtype of
Product[] that is not a behavioral subtype: here Java itself
breaks Lecture 14's rule. To catch such a store, Java checks
every store into an array of objects, when the program runs.
That check is the second of the two exceptions that A queue of
anything counted. javac accepts line 22,
which could break type safety, and Java makes the check when the
program runs. As with the cast, type safety still holds: the
skittles never reach the array. And as with the cast, the price
is a program that can fail on some run.
A wildcard asks for less
Back to total(deals). javac was right to refuse it: a method
that takes a List<Product> may add to the list. But our total
never adds. Its parameter's type, List<Product>, allows an add
that its body never makes.
Lecture 2 said of its specifications A and D that A asks more of
its client than D does, which made D the stronger
of the two. Our total is in A's place: it asks its client for a
list that can be added to. So let total ask for less. Here it
is with one change, in the type of its parameter:
| Carts.java | |
|---|---|
38 39 40 41 42 43 44 45 | |
The ? is a wildcard: it stands for a type that the method
does not name. Read List<? extends Product> as a list of some
one type, which is Product or a Java subtype of Product. A
List<Product> is such a list. So is a List<SaleProduct>.
The rule of the last section is unchanged. Its A and B are types
written out, such as Product and SaleProduct. A wildcard is not
one type: ? extends Product stands for any one of many. So a
List<SaleProduct> is still not a List<Product>. But it is a
List<? extends Product>, which is a different type. In a
List<Product>, an element may be a SaleProduct, but the type
argument is known: it is Product, so add takes any Product. In
a List<? extends Product>, the type argument itself is
not known.
Now javac accepts both calls, and each prints its total:
$ javac Carts.java
$ java Carts
total(cart): $3.0
total(deals): $2.0
deals: [chips: $1.5, gum: $0.5, mints: $0.5]
cart: [skittles: $2.0, m&ms: $1.0, mints: $0.5]
(The last two lines come from a second method, below.)
With the wildcard, the body of total may no longer add to
cart. A copy in which total adds the bag for ten cents, as
line 40, does not compile:
$ javac Carts.java
Carts.java:40: error: incompatible types: Product cannot be converted to CAP#1
cart.add(new Product("bag", 0.1));
^
where CAP#1 is a fresh type-variable:
CAP#1 extends Product from capture of ? extends Product
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
CAP#1 is javac's name for the type that the ? stands for.
On line 40, cart is a list of CAP#1s. All javac knows about
CAP#1 is that it is Product or a Java subtype of Product. Since
CAP#1 might be SaleProduct, or a class not yet written, javac
refuses the call.
The body may still read. Whatever type the ? stands for, an
element of the list is a Product. So the loop on line 41 may
store each element in a variable declared a Product.
The other direction. A method that only adds can ask for less in the other direction. Here is one, which puts a pack of mints, on sale, at the end of a list:
| Carts.java | |
|---|---|
47 48 49 50 51 52 53 | |
Read List<? super SaleProduct> as a list of some one type,
which is SaleProduct or a Java supertype of SaleProduct. That
could be a List<SaleProduct>, a List<Product>, or a
List<Object>. A SaleProduct may be added to each of the three.
So javac accepts line 52, and main calls addMints on both
of its lists:
| Carts.java | |
|---|---|
32 33 34 35 | |
The last two lines of the run above show the mints at the end of each list.
Through list, javac does not know the type of an element
that comes out. A copy in which addMints reads the first
element into a variable declared a SaleProduct, as line 53, does
not compile:
$ javac Carts.java
Carts.java:53: error: incompatible types: CAP#1 cannot be converted to SaleProduct
SaleProduct first = list.get(0);
^
where CAP#1 is a fresh type-variable:
CAP#1 extends Object super: SaleProduct from capture of ? super SaleProduct
1 error
Since the list might be a List<Object>, all javac knows about
an element is that it is an Object.
Being the compiler. How do we decide whether javac accepts
a call through a wildcard? javac does not know which list the
variable refers to. So it accepts a call only if the call is
right for every list that the variable may refer to, a list of a
class not yet written included. To see that
javac refuses a call, find one list that the call would be
wrong for. Here are three elements and three lists, the
parameters of one method:
| Table.java | |
|---|---|
20 21 22 23 | |
Before you read the table, decide for each call of add and
get, through each of the three lists, whether javac accepts
it. The table gives javac's answers:
| the call | cart |
from |
to |
|---|---|---|---|
add(o) |
refused | refused | refused |
add(p) |
accepted | refused | refused |
add(s) |
accepted | refused | accepted |
o = get(0) |
accepted | accepted | accepted |
p = get(0) |
accepted | accepted | refused |
s = get(0) |
refused | refused | refused |
Two of its cells have split the room in past quarters of this course:
from.add(s)is refused. A SaleProduct is a Product, so the call looks safe. Butfromis a list of some one type, andjavacdoes not know which. Say someone later writes ClearanceProduct, a subclass of SaleProduct. Thenfrommay be aList<ClearanceProduct>, and a plain SaleProduct does not belong in that list. Lecture 14 said of SaleProduct that it could have been written a year aftertotal, by someone else, and so could ClearanceProduct. So throughfrom,javacaccepts noaddof any object, whatever its class.to.add(o)is refused. Since Object is a supertype of SaleProduct, a reader may think the list could hold an Object. Buttomay be aList<SaleProduct>, andomay refer to an object that does not belong in that list. Neither does a plain Product, soto.add(p)is refused too.
One value can be added through all three lists: null, which
javac accepts in place of an object of any type, CAP#1
included. So from.add(null) compiles.
Rule: get through extends, add through super
A List<? extends A> is a list of some one type that is A or
a Java subtype of A. Its get returns an A, and its add
accepts nothing but null.
A List<? super B> is a list of some one type that is B or a
Java supertype of B. Its add accepts a B, and its get
returns an Object.
So a method that only reads from a list takes a
List<? extends A>, a method that only adds to one takes a
List<? super B>, and a method that does both takes
a List<A>.
Here is the rule as a picture of which list javac
accepts where:
Three lists, and the two wildcard types that accept them. Each arrow between two of the three lists is crossed, since their type arguments differ.
Naftalin and Wadler call the rule the Get and Put Principle.
Joshua Bloch's mnemonic for it is PECS: producer extends,
consumer super.9 (The producer or consumer is the list: a
list that total reads from produces Products for total.) The
JDK uses the rule in its own signatures. List<E>'s method
addAll takes a Collection<? extends E>, so a List<Product>
can take in all of a List<SaleProduct> in one call.11
A wildcard with no bound. A ? may stand alone. Read
List<?> as a list of some one type, any type at all. It means
the same as List<? extends Object>, so its get returns an
Object, and its add accepts nothing but null:10
| Table.java | |
|---|---|
41 42 43 44 | |
A wildcard or a name? total could have named its type, as
cheapest did: <P extends Product>, with a parameter of type
List<P>. That signature accepts the cart and the deals too.
The wildcard is for a type that the method never needs to name
again. cheapest needs the name, to say that the product it
returns has the same type as the elements of its list.
When the program runs
Lecture 16 said this of a HashSet, in passing: inside the
set, an element has the type Object, whatever the set's
declared element type. In the source, that is the
rule of What the code may do with an
E: the code of HashSet<E> may
use an E only as an Object. The sentence is also true of the
running program, for a reason this section gives.
javac checks the type arguments, and then it leaves them out
of the code that runs. The compiled ListQueue is one class, for
every type argument, and inside it an element is an Object, as it
was in ObjectQueue. Where a client needs a String back, javac
puts in the cast that we wrote by hand at the start of this
lecture. This is called type erasure.12 The
difference from ObjectQueue is that javac checked every call
of join first. So in this lecture's programs, the casts that
javac puts in do not fail. (A program can get around the check:
with a type written with no type argument at all, such as a plain
List, or with a cast to a type such as List<String>. javac
calls such a line unchecked, and after one, a cast that javac
put in can fail.)
Here is a program that looks at two queues while it runs:
| Erasure.java | |
|---|---|
20 21 22 23 24 25 26 27 28 | |
getClass returns the class of an object. Line 27 asks whether
o is a Queue<?>, a queue of some type. Here is the run:
$ javac Erasure.java
$ java Erasure
names: class ListQueue
tickets: class ListQueue
Queue<?>: true
Both objects have the class ListQueue, with no type argument.
When the program runs, nothing about either object says String
or Integer.
Java made this choice in 2004, to fit type parameters into a
language whose programs had been compiled and shipped for years.
A class compiled before type parameters and a class compiled
after can use the same ArrayList, since there is only one.
Since the type arguments are gone, a running program cannot ask
an object for its type argument. You will meet this in
two places:
-
instanceofcannot check a type argument when the program runs. A copy whose line 27 asks whetherois aQueue<String>does not compile:$ javac Erasure.java Erasure.java:27: error: Object cannot be safely cast to Queue<String> System.out.println("Queue<String>: " + (o instanceof Queue<String>)); ^ 1 errorAn
instanceofasks whetherocould be cast to the type, sojavacreads the question as a cast from Object toQueue<String>. Nothing could check such a cast when the program runs, and the message says so. The question that can be answered is the one line 27 asks: isoa queue of some type? That matters for theequalsof Lecture 16, which opens withinstanceof. In a generic class, thatinstanceofasks for aQueue<?>, as exercise 5 does. -
A generic class cannot write
new E[10].javacrefuses it, with the message generic array creation. Each array keeps the class it was made to hold, for Java to check each store against, as it checked the store in ArrayCarts.java. When the program runs there is noEfor an array to keep.ListQueuewrites no array: it keeps its elements in aList<E>.
So a type argument is checked when javac compiles the program,
and it is gone from the code that runs. What is left of it then is
the casts that javac put in.
Exercises
Each answer is folded under its question. Write yours first.
1. Lecture 10's second implementation of the help queue,
LinkedQueue, keeps its students in a chain of nodes. Here is
how the class opens:
| LinkedQueue.java | |
|---|---|
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
Give LinkedQueue a type parameter, so that it implements this
lecture's Queue<E>. Which of
these lines change, and how? One class here needs more than a
String turned into an E. Which one, and why?
Answer
-
Every String becomes
E, andNodegets a type parameter too. Here are the same lines of the generic class. Its fieldnetidis renamedelement, since an element need not be a student:GenericLinked.java 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
/** A queue kept as a chain of nodes, one node per element. */ class LinkedQueue<E> implements Queue<E> { /** One element waiting, and the node of the element after it. */ private static class Node<E> { E element; Node<E> next; // null for the last node in the chain Node(E element) { this.element = element; } } // The first and last nodes of the chain, both null when nothing // is waiting. private Node<E> front; private Node<E> back; -
Nodeis the class that needs more. A node holds an element, so a node's type depends on the element type as the queue's does.Nodeis declaredstatic, which means that a node belongs to no one queue. So the code ofNodecannot use the queue'sE. If it tries, withE element;in aNodethat declares no type parameter,javacrefuses the field: it says non-static type variable E cannot be referenced from a static context.Nodedeclares a type parameter itself, on line 62. Every use ofNodethen writes a type argument: the fieldsfrontandbackare each aNode<E>, andjoinmakes anew Node<E>(e). -
What does not change.
sizestill returns anint, since it counts the elements, whatever their type. Only a String that stood for an element becomes anE. -
The run.
mainjoins ada and eve to aLinkedQueue<String>, printswaiting(), and callsnextonce. Then it joins 41 and 42 to aLinkedQueue<Integer>:$ javac GenericLinked.java $ java GenericLinked waiting: [ada, eve] next: ada tickets: [41, 42]
2. Write a method max that returns the largest element of a
list, as compareTo orders the elements. a.compareTo(b)
returns a number greater than 0 when a is greater than b. The
method should work on a List<String> and on a List<Integer>.
What is its type parameter's bound? Then say whether javac
accepts a call of max on a List<Product>. (Product is
declared with no implements.)
Answer
-
The bound is
Comparable<T>. The body callscompareToon aT, and passes it anotherT.Comparable<T>is the interface that declarescompareTowith a parameter of typeT, so that is what aThas to be:Max.java 23 24 25 26 27 28 29 30 31 32 33 34 35 36
/** * @requires list is not empty * @return an element of list that compareTo puts no other * element of list above */ public static <T extends Comparable<T>> T max(List<T> list) { T best = list.get(0); for (T t : list) { if (t.compareTo(best) > 0) { best = t; } } return best; }The body is
cheapest's, withcompareToin place of the two calls ofgetPrice. -
The run, on the list eve, ada, bob and the list 41, 7, 42:
$ javac Max.java $ java Max max(names): eve max(tickets): 42 -
javacrefusesmaxon aList<Product>. For that callTwould be Product, and Product does not implementComparable<Product>. The bound is the method's precondition on its type argument, and this call does not meet it. If Product were declaredimplements Comparable<Product>, with acompareTothat compares prices, the call would compile. -
The JDK's own
maxasks for less.Collections.maxbounds itsTbyComparable<? super T>: aTcan be compared by acompareTowritten forTor for a supertype ofT. So it also accepts a list of a class that inherits itscompareTo, as SaleProduct would inherit Product's. With our bound,javacwould refusemaxon aList<SaleProduct>, since a SaleProduct would be aComparable<Product>and not aComparable<SaleProduct>.13
3. Ask the compiler, on paper. Here are eight variables:
| Assignments.java | |
|---|---|
19 20 21 22 23 | |
For each of these ten assignments, say whether javac accepts
it. An assignment x = y is accepted when the type of y is a
Java subtype of the type of x.
cart = made;cart = deals;things = cart;from = deals;from = things;to = things;to = deals;products = sales;sales = products;deals = from;
Answer
-
javacaccepts five of the ten:the assignment accepted? why 1 cart = madeyes ArrayList implements List 2 cart = dealsno Product and SaleProduct differ 3 things = cartno Object and Product differ 4 from = dealsyes SaleProduct extends Product 5 from = thingsno Object is not a subtype of Product 6 to = thingsyes SaleProduct is a subtype of Object 7 to = dealsyes SaleProduct is SaleProduct 8 products = salesyes arrays took the other answer 9 sales = productsno a Product is not a SaleProduct 10 deals = fromno frommay be aList<Product> -
Assignments 2 and 3 are the lecture's rule. A
List<SaleProduct>is not aList<Product>, and by the same rule aList<Product>is not aList<Object>. The second surprises people who have just accepted the first. But the rule makes no exception for Object. -
Assignment 10 runs the wildcard backward. A
List<SaleProduct>is accepted where aList<? extends Product>is expected, as in assignment 4. The other way around,javacknows only thatfromis a list of some subtype of Product, and that is not enough to call it a list of sale products.
4. Be the compiler once more, this time on the JDK's own types. Integer and Double are each a Java subtype of Number, a class of the JDK's, and neither is a subtype of the other. Here are seven variables:
| Numbers.java | |
|---|---|
18 19 20 21 | |
For each of these twelve lines, say whether javac accepts it.
For each one it refuses, name a list that the line would be
wrong for.
nums.add(i);nums.add(d);nums.add(o);n = from.get(0);i = from.get(0);from.add(i);from.add(null);to.add(i);to.add(n);to.add(d);o = to.get(0);n = to.get(0);
Answer
-
javacaccepts six of the twelve:the line accepted? a list it would be wrong for 1 nums.add(i)yes 2 nums.add(d)yes 3 nums.add(o)no numsitself, aList<Number>4 n = from.get(0)yes 5 i = from.get(0)no a List<Double>6 from.add(i)no a List<Double>7 from.add(null)yes 8 to.add(i)yes 9 to.add(n)no a List<Integer>10 to.add(d)no a List<Integer>11 o = to.get(0)yes 12 n = to.get(0)no a List<Object> -
Lines 1 and 2 need no wildcard.
numsis aList<Number>, so itsaddtakes a Number, and an Integer or a Double is accepted where a Number is expected. That is Lecture 14's subtyping, applied to one element. The rule of this lecture is about a list passed where a list is expected, and lines 1 and 2 pass an element. -
Line 12 is refused. Everything that
to.addaccepts is an Integer, so a reader may expect a Number to come out. Buttomay be aList<Object>that already held a String before we were handed it.
5. Give ListQueue<E> the equals and the hashCode of
Lecture 16: two queues are equal when the same
elements are waiting in each, in the same order. What does the
instanceof in equals ask for? Then predict the three lines
this main prints:
| EqualQueue.java | |
|---|---|
17 18 19 20 21 22 23 24 25 26 27 28 29 | |
Answer
-
The two methods are Lecture 16's, with one change:
EqualQueue.java 96 97 98 99 100 101 102 103 104 105 106 107
@Override public boolean equals(Object o) { if (o instanceof Queue<?> q) { return waiting().equals(q.waiting()); } return false; } @Override public int hashCode() { return waiting().hashCode(); }As in that lecture, the interface
Queue<E>declares both methods too, to say what every implementation must do. -
The
instanceofasks for aQueue<?>, a queue of some type. It cannot ask for aQueue<E>: when the program runs,ocarries no type argument to check. Soqis a queue of elements of a type thatequalsdoes not know, andq.waiting()is a list of them.equalsneeds nothing more about them, sinceList'sequalscompares the two lists element by element, by calling each element's ownequals. -
The run:
$ javac EqualQueue.java $ java EqualQueue both empty: true ada and 41: false ada and ada: true -
An empty queue of Strings equals an empty queue of Integers. Both stand for the queue with nobody in it, and nothing in either object says String or Integer when the program runs. Once each has an element,
List'sequalscompares the two elements by callingequalson one of them, and the String"ada"does not equal the Integer 41.
6. Here is a method that adds the elements of one list at the
end of another. It declares a type parameter T, and each of its
parameters is a List<T>:
| Copy.java | |
|---|---|
32 33 34 35 36 37 38 39 40 41 | |
main makes the cart and the deals of the lecture, and then
calls copy(deals, cart) on line 28, to add the deals to the
cart. javac refuses that call. Why? Then change the types of
copy's two parameters, and nothing else, so that javac
accepts the call. Is there more than one way? (The repo keeps
this version as CopyExact.txt, since javac refuses it.)
Answer
-
No one
Tfits both lists. Both parameters are aList<T>, with the sameT.dealsis aList<SaleProduct>, soTwould have to be SaleProduct.cartis aList<Product>, soTwould have to be Product. By the lecture's rule, neither list is accepted as the other's type, andjavacsays so:$ javac Copy.java Copy.java:28: error: method copy in class Copy cannot be applied to given types; copy(deals, cart); ^ required: List<T>,List<T> found: List<SaleProduct>,List<Product> reason: inference variable T has incompatible equality constraints Product,SaleProduct where T is a type-variable: T extends Object declared in method <T>copy(List<T>,List<T>) 1 error -
copyonly reads fromsrc, and only adds todst. So the rule box gives each parameter a wildcard: get throughextends, add throughsuper.Copy.java 37static <T> void copy(List<? extends T> src, List<? super T> dst) {Now a
Tfits both lists. Product does. The deals are a list of a subtype of Product. The cart is a list of Product itself. -
The run. The two deals are at the end of the cart:
$ javac Copy.java $ java Copy cart: [skittles: $2.0, m&ms: $1.0, chips: $1.5, gum: $0.5] -
Either wildcard alone is enough for this call. With
srcaList<? extends T>anddstaList<T>,Tis Product. WithsrcaList<T>anddstaList<? super T>,Tis SaleProduct. The JDK's ownCollections.copywrites both wildcards.14
7. Exercise 6 ended with four ways to declare copy. Each
declares <T>, and all four have the same body and the same
Javadoc. They differ only in the types of the two parameters:
src |
dst |
|
|---|---|---|
| A | List<T> |
List<T> |
| B | List<? extends T> |
List<T> |
| C | List<T> |
List<? super T> |
| D | List<? extends T> |
List<? super T> |
A client has four lists: cart and more, each a
List<Product>; deals, a List<SaleProduct>; and things, a
List<Object>. For each of these four calls, say under which of
the four declarations javac accepts it:
copy(cart, more);copy(deals, cart);copy(deals, things);copy(cart, deals);
Then compare the four declarations as Lecture 2 compared specifications. Which one asks the most of its client?
Answer
-
javac's answers, with yes for a call it accepts:the call A B C D 1 copy(cart, more)yes yes yes yes 2 copy(deals, cart)no yes yes yes 3 copy(deals, things)no yes yes yes 4 copy(cart, deals)no no no no -
A asks the most of its client. Under A, both lists must have the same type argument, so
javacrefuses calls 2 and 3. Every call that A accepts, B, C, and D accept too, with the sameT: aList<T>is accepted where aList<? extends T>or aList<? super T>is expected. So each of B, C, and D accepts every call that A does, and more. The precondition of each is weaker than A's. The postcondition of each is the same as A's. By Lecture 2's rule, each of B, C, and D is a stronger specification than A. -
B, C, and D agree on these four calls. D is the one that the rule box gives. Its types say, of each parameter, the one thing the body does with it:
copyreads fromsrcand adds todst. -
Call 4 is refused under all four, and
javacis right. The call would add the cart's products todeals, and a cart may hold a plain Product. Under D,Twould have to be a supertype of Product and a subtype of SaleProduct, and no type is both.
Further reading
- The tutorial that introduced type parameters to Java programmers. Gilad Bracha, "Generics in the Java Programming Language" (2004), 23 pages. Its sections 1 to 5 cover casts, generic classes, subtyping, wildcards, and generic methods, in that order. Its sections 6 and 7 go on to what this lecture left out or only touched: a class compiled before type parameters used beside one compiled after, and the fine print of erasure.
- Advice on using them well. Joshua Bloch, Effective Java,
third edition (2018), chapter 5, "Generics", items 26 to 33.
Item 28, "Prefer lists to arrays", sets the two answers of the
sections A list of sale products is not a list of
products
and Arrays took the other
answer side by side. Item 31,
"Use bounded wildcards to increase API flexibility", is the
rule of the section A wildcard asks for
less, with a stack whose
pushAlltakes? extends Eand whosepopAlltakes? super E. - The rule, argued from examples. Maurice Naftalin and Philip Wadler, Java Generics and Collections (2006), chapter 2, "Subtyping and Wildcards". Section 2.4 is the Get and Put Principle, and section 2.5 compares arrays with lists.
- Oracle's lesson. Oracle, The Java Tutorials, "Generics", written for JDK 8. Its last page, "Questions and Exercises: Generics", has short questions, and links to their answers.
-
The angle brackets came to Java in version 5, released in 2004. Gilad Bracha, "Generics in the Java Programming Language" (2004), is a tutorial written to introduce them. Its section 1, "Introduction", shows a cast of the kind this section's client writes,
(Integer) myIntList.iterator().next(), and says that such a cast introduces "the possibility of a run time error". ↩ -
The Java Language Specification, Java SE 25 edition, section 4.12.2, "Variables of Reference Type": "A variable of a class type T can hold a null reference or a reference to an instance of class T or of any class that is a subclass of T." Section 5.1.6.3 says of a cast such as the one on line 29 that it needs "a validity check at run time". Section 4.12.2 goes on to say that the claim is not guaranteed for a type with a type argument, once a program has a line that
javacwarns about as unchecked. ↩ -
Oracle, The Java Tutorials, "Generic Types", under "Type Parameter Naming Conventions". ↩
-
The Java Language Specification, Java SE 25 edition, section 9.1.2, "Generic Interfaces and Type Parameters" (section 8.1.2 says the same of classes), and section 4.5, "Parameterized Types", which is its name for a type such as
List<String>. The declaration ofListis in the documentation ofjava.util.Listin Java 25. ↩ -
The documentation of
java.lang.Comparable, ofString, and ofIntegerin Java 25. The last two list the interfaces each class implements. ↩ -
The comparison reads only the types. The documentation of
java.util.Listin Java 25 lets a list'saddrefuse an element with an exception, for one, "if the class of the specified element prevents it from being added to this list". ↩ -
The Java Language Specification, Java SE 25 edition, section 4.10.2, "Subtyping among Class and Interface Types", and section 4.5.1, "Type Arguments of Parameterized Types". ↩
-
The Java Language Specification, Java SE 25 edition, section 4.10.3, "Subtyping among Array Types", and section 10.5, "Array Store Exception". ↩
-
Maurice Naftalin and Philip Wadler, Java Generics and Collections (2006), section 2.4, "The Get and Put Principle". Joshua Bloch, Effective Java, third edition (2018), item 31, "Use bounded wildcards to increase API flexibility". ↩
-
The Java Language Specification, Java SE 25 edition, section 4.5.1, "Type Arguments of Parameterized Types": "The wildcard
? extends Objectis equivalent to the unbounded wildcard?." ↩ -
The documentation of
List.addAllin Java 25. ↩ -
The Java Language Specification, Java SE 25 edition, section 4.6, "Type Erasure", and section 4.7, "Reifiable Types", which gives the reason for the choice: "compatibility with existing code". ↩
-
The documentation of
Collections.maxin Java 25. Its type parameter is declared<T extends Object & Comparable<? super T>>, and its parameter is aCollection<? extends T>. ↩ -
The documentation of
Collections.copyin Java 25. It takes the destination first: its parameters are aList<? super T>and then aList<? extends T>. ↩