CSE331�Lecture 17.1-17.3-18
Generics: overview, methods, subtyping, bounds, and wildcards
Ardi Madadi �Summer 2021�(Based on slides by Kevin Zatloukal, Mike Ernst, Dan Grossman, and many others)
1
Preface
CSE 331 Summer 2021
Pre-generic Collection Use
Checking types were correct was done at run-time.
Hashtable h = new Hashtable();
h.put(“abc”, new Integer(3));
...
Integer val = (Integer) h.get(“abc”);
No compiler help to ensure type constraints are satisfied
CSE 331 Summer 2021
Alternative: Many, Many Classes
interface ListOfNumbers {
boolean add(Number elt);
Number get(int index);
}
interface ListOfIntegers {
boolean add(Integer elt);
Integer get(int index);
}
… and many, many more
CSE 331 Summer 2021
Why we love abstraction
Hide details
Give a meaningful name to a concept (readability)
Permit reuse in new contexts
CSE 331 Summer 2021
Varieties of abstraction
Abstraction over computation: procedures (methods)
int x1, y1, x2, y2;
Math.sqrt(x1*x1 + y1*y1);
Math.sqrt(x2*x2 + y2*y2);
Abstraction over data: ADTs (classes, interfaces)
Point p1, p2;
Abstraction over types: polymorphism (generics)
Point<Integer>, Point<Double>
CSE 331 Summer 2021
Related abstractions
interface ListOfNumbers {
boolean add(Number elt);
Number get(int index);
}
interface ListOfIntegers {
boolean add(Integer elt);
Integer get(int index);
}
… and many, many more
// abstracts over element type
interface List<E> {
boolean add(E n);
E get(int index);
}
CSE 331 Summer 2021
Lets us use types
List<Integer>
List<Number>
List<String> List<List<String>> …
An analogous parameter
interface ListOfIntegers {
boolean add(Integer elt);
Integer get(int index);
}
interface List<E> {
boolean add(E n);
E get(int index);
}
CSE 331 Summer 2021
Integer -> boolean
Type variables are types
class NewSet<T> implements Set<T> {
// rep invariant:
// non-null, contains no duplicates
// …
List<T> theRep;
T lastItemInserted;
…
}
CSE 331 Summer 2021
Declaration
Use
Use
Use
Declaring and instantiating generics
class Name<TypeVar1, …, TypeVarN> {…}
interface Name<TypeVar1, …, TypeVarN> {…}
To instantiate a generic class/interface, supply type arguments:
Name<Type1, …, TypeN>
CSE 331 Summer 2021
Restricting instantiations by clients
boolean add1(Object elt);
boolean add2(Number elt);
add1(new Date()); // OK
add2(new Date()); // compile-time error
interface List1<E extends Object> {…}
interface List2<E extends Number> {…}
List1<Date> // OK, Date is a subtype of Object
List2<Date> // compile-time error, Date is not a
// subtype of Number�
CSE 331 Summer 2021
Upper bounds
Revised definition
class Name<TypeVar1 extends Type1,
...,
TypeVarN extends TypeN> {…}
To instantiate a generic class/interface, supply type arguments:
Name<Type1, …, TypeN>
Compile-time error if type is not a subtype of the upper bound
CSE 331 Summer 2021
Using type variables
Code can perform any operation permitted by the bound
class Foo1<E extends Object> {
void m(E arg) {
arg.intValue(); // compiler error, E might not� // support intValue
}
}
class Foo2<E extends Number> {
void m(E arg) {
arg.intValue(); // OK, since Number and its� // subtypes support intValue
}
}
CSE 331 Summer 2021
More examples
public class Graph<N> implements Iterable<N> {
private final Map<N, Set<N>> node2neighbors;
public Graph(Set<N> nodes, Set<Pair<N,N>> edges){
…
}
}
public interface Path<N, P extends Path<N,P>>
extends Iterable<N>, Comparable<Path<?, ?>> {
public Iterator<N> iterator();
…
}
(Note: you probably don’t want to use this code in your homework.)
CSE 331 Summer 2021
More bounds
<TypeVar extends SuperType>
<TypeVar extends ClassA & InterfaceB & InterfaceC & …>
Example:
// tree set works for any comparable type
public class TreeSet<T extends Comparable<T>> {
…
}
CSE 331 Summer 2021
Where are we?
CSE 331 Summer 2021
Not all generics are for collections
class Utils {
static double sumList(List<Number> lst) {
double result = 0.0;
for (Number n : lst) {
result += n.doubleValue();
}
return result;
}
static Number choose(List<Number> lst) {
int i = … // random number < lst.size
return lst.get(i);
}
}
CSE 331 Summer 2021
Weaknesses
CSE 331 Summer 2021
Much better
class UtilsGenerified {
static <T extends Number>
double sumList(List<T> lst) {
double result = 0.0;
for (Number n : lst) { // T also works
result += n.doubleValue();
}
return result;
}
static <T>
T choose(List<T> lst) {
int i = … // random number < lst.size
return lst.get(i);
}
}
CSE 331 Summer 2021
Have to declare type parameter(s)
Have to declare type parameter(s)
Using generics in methods
CSE 331 Summer 2021
More examples
<T extends Comparable<T>> T max(Collection<T> c) {
return Collections.max(c);
}
<T extends Comparable<T>>
void sort(List<T> list) {
Collections.sort(list);
}
(This works but will be even more useful later with more bounds)
<T> void copyTo(List<T> dst, List<T> src) {
dst.addAll(src);
}
CSE 331 Summer 2021
Where are we?
CSE 331 Summer 2021
Generics and subtyping
CSE 331 Summer 2021
Number
Integer
List<Number>
List<Integer>
?
Generics and subtyping
List<Number> numList = new List<Number>();
List<Integer> intList = new List<Integer>();
intList.add(new Integer(3));
-> numList.add(new Integer(3));
numList.add(new Double(3.0));
-> intList.add(new Double(3.0));
Number n = numList.get(0);
-> Number n = intList.get(0);
Integer n = intList.get(0);
-> Integer n = numList.get(0);
Neither type can be substituted for the other legally in all situations!
CSE 331 Summer 2021
Generics and subtyping
List<Number> numList = new List<Number>();
List<Integer> intList = new List<Integer>();
intList.add(new Integer(3));
-> numList.add(new Integer(3)); // okay
numList.add(new Double(3.0));
-> intList.add(new Double(3.0));
Number n = numList.get(0);
-> Number n = intList.get(0);
Integer n = intList.get(0);
-> Integer n = numList.get(0);
Neither type can be substituted for the other legally in all situations!
CSE 331 Summer 2021
Generics and subtyping
List<Number> numList = new List<Number>();
List<Integer> intList = new List<Integer>();
intList.add(new Integer(3));
-> numList.add(new Integer(3)); // okay
numList.add(new Double(3.0));
-> intList.add(new Double(3.0)); // not legal
Number n = numList.get(0);
-> Number n = intList.get(0);
Integer n = intList.get(0);
-> Integer n = numList.get(0);
Neither type can be substituted for the other legally in all situations!
CSE 331 Summer 2021
Generics and subtyping
List<Number> numList = new List<Number>();
List<Integer> intList = new List<Integer>();
intList.add(new Integer(3));
-> numList.add(new Integer(3)); // okay
numList.add(new Double(3.0));
-> intList.add(new Double(3.0)); // not legal
Number n = numList.get(0);
-> Number n = intList.get(0); // okay
Integer n = intList.get(0);
-> Integer n = numList.get(0);
Neither type can be substituted for the other legally in all situations!
CSE 331 Summer 2021
Generics and subtyping
List<Number> numList = new List<Number>();
List<Integer> intList = new List<Integer>();
intList.add(new Integer(3));
-> numList.add(new Integer(3)); // okay
numList.add(new Double(3.0));
-> intList.add(new Double(3.0)); // not legal
Number n = numList.get(0);
-> Number n = intList.get(0); // okay
Integer n = intList.get(0);
-> Integer n = numList.get(0); // illegal
Neither type can be substituted for the other legally in all situations!
CSE 331 Summer 2021
List<Number> and List<Integer>
interface List<T> {
boolean add(T elt);
T get(int index);
}
So type List<Number> has:
boolean add(Number elt);
Number get(int index);
So type List<Integer> has:
boolean add(Integer elt);
Integer get(int index);
�Java subtyping is invariant with respect to generics
CSE 331 Summer 2021
Number
Integer
Hard to remember?
If Type2 and Type3 are different,
then Type1<Type2> is not a subtype of Type1<Type3>
Previous example shows why:
If our types have only observers or only mutators, then one direction of subtyping would be sound
CSE 331 Summer 2021
Read-only allows covariance
interface List<T> {
T get(int index);
}
So type List<Number> has:
Number get(int index);
So type List<Integer> has:
Integer get(int index);
So covariant subtyping would be correct:
But Java does not analyze interface definitions like this
CSE 331 Summer 2021
Number
Integer
List<Number>
List<Integer>
covariance
Write-only allows contravariance
interface List<T> {
boolean add(T elt);
}
So type List<Number> has:
boolean add(Number elt);
So type List<Integer> has:
boolean add(Integer elt);
So contravariant subtyping would be correct:
But Java does not analyze interface definitions like this
CSE 331 Summer 2021
Number
Integer
List<Number>
List<Integer>
contravariance
Co- and Contra-variance
interface List<T> {
boolean add(T elt);
T get(int index);
}
In general, List<T> should be
Some languages (e.g., Scala and C#) allow this
Java does not:
CSE 331 Summer 2021
About the parameters
CSE 331 Summer 2021
Where are we?
CSE 331 Summer 2021
Best type for addAll
Class AlternateSet<E> {
// Adds all elements in c to this set
// (that are not already present)
void addAll(_______ c);
}
What is the best type for addAll’s parameter?
CSE 331 Summer 2021
Best type for addAll
Class AlternateSet<E> {
// Adds all elements in c to this set
// (that are not already present)
void addAll(_______ c);
}
void addAll(Set<E> c);
Too restrictive:
CSE 331 Summer 2021
Best type for addAll
Class AlternateSet<E> {
// Adds all elements in c to this set
// (that are not already present)
void addAll(_______ c);
}
void addAll(Collection<E> c);
Still too restrictive:
CSE 331 Summer 2021
Best type for addAll
Class AlternateSet<E> {
// Adds all elements in c to this set
// (that are not already present)
void addAll(_______ c);
}
<T extends E> void addAll(Collection<T> c);
The fix: bounded generic type parameter
CSE 331 Summer 2021
More verbose first
Now:
Then: Java wildcards
CSE 331 Summer 2021
Generic methods get around invariance
You cannot pass List<Integer> to method expecting List<Number>
Get around it by making your method generic:
<T extends Number> double sumList(List<T> nums) {
double s = 0;
for (T t : nums)
s += t.doubleValue();
return s;
}
CSE 331 Summer 2021
Revisit copy method
Earlier we saw this:
<T> void copyTo(List<T> dst, List<T> src) {
for (T t : src)
dst.add(t);
}
Now we can do this (which is more general):
<T1, T2 extends T1> void copyTo(List<T1> dst,
List<T2> src) {
for (T2 t : src)
dst.add(t);
}
CSE 331 Summer 2021
Where are we?
CSE 331 Summer 2021
Examples
[Compare to earlier version]
interface Set<E> {
void addAll(Collection<? extends E> c);
}
<T extends E> void addAll(Collection<T> c);
CSE 331 Summer 2021
Wildcards
Syntax: for a type-parameter instantiation (inside the <…>), can write:
A wildcard is essentially an anonymous type variable
CSE 331 Summer 2021
More examples
<T extends Comparable<T>> T max(Collection<T> c);
CSE 331 Summer 2021
Wildcards
Syntax: for a type-parameter instantiation (inside the <…>), can write:
A wildcard is essentially an anonymous type variable
CSE 331 Summer 2021
More examples
<T> void copyTo(List<? super T> dst, � List<? extends T> src) {
for (T t : src)
dst.add(t);
}
Why this works:
CSE 331 Summer 2021
PECS: Producer Extends, Consumer Super
Should you use extends or super or neither?
<T> void copyTo(List<? super T> dst, � List<? extends T> src);
CSE 331 Summer 2021
More on lower bounds
<T super Foo> void m(Bar<T> x);
CSE 331 Summer 2021
? versus Object
? indicates a particular but unknown type
void printAll(List<?> lst) {…}
Difference between List<?> and List<Object>:
Difference between List<Foo> and List<? extends Foo>:
Example: List<? extends Animal> might store only Giraffes only (no Zebras)
Example: List<Animal> could store Giraffes and Zebras
CSE 331 Summer 2021
Legal operations on wildcard types
Object o;
Number n;
NewInteger i;
PositiveInteger p;
List<? extends NewInteger> lei;
First, which of these is legal?
lei = new ArrayList<Object>();
lei = new ArrayList<Number>();
lei = new ArrayList<NewInteger>();
lei = new ArrayList<PositiveInteger>();
lei = new ArrayList<NegativeInteger>();
CSE 331 Summer 2021
Legal operations on wildcard types
Object o;
Number n;
NewInteger i;
PositiveInteger p;
List<? extends NewInteger> lei;
First, which of these is legal?
lei = new ArrayList<Object>();
lei = new ArrayList<Number>();
lei = new ArrayList<NewInteger>();
lei = new ArrayList<PositiveInteger>();
lei = new ArrayList<NegativeInteger>();
CSE 331 Summer 2021
Legal operations on wildcard types
Object o;
Number n;
NewInteger i;
PositiveInteger p;
List<? extends NewInteger> lei;
First, which of these is legal?
lei = new ArrayList<Object>();
lei = new ArrayList<Number>();
lei = new ArrayList<NewInteger>();
lei = new ArrayList<PositiveInteger>();
lei = new ArrayList<NegativeInteger>();
Which of these is legal?
o = lei.get(0);
n = lei.get(0);
i = lei.get(0);
p = lei.get(0);
CSE 331 Summer 2021
Legal operations on wildcard types
Object o;
Number n;
NewInteger i;
PositiveInteger p;
List<? extends NewInteger> lei;
First, which of these is legal?
lei = new ArrayList<Object>();
lei = new ArrayList<Number>();
lei = new ArrayList<NewInteger>();
lei = new ArrayList<PositiveInteger>();
lei = new ArrayList<NegativeInteger>();
Which of these is legal?
o = lei.get(0);
n = lei.get(0);
i = lei.get(0);
p = lei.get(0);
CSE 331 Summer 2021
Legal operations on wildcard types
Object o;
Number n;
NewInteger i;
PositiveInteger p;
List<? extends NewInteger> lei;
First, which of these is legal?
lei = new ArrayList<Object>();
lei = new ArrayList<Number>();
lei = new ArrayList<NewInteger>();
lei = new ArrayList<PositiveInteger>();
lei = new ArrayList<NegativeInteger>();
Which of these is legal?
o = lei.get(0);
n = lei.get(0);
i = lei.get(0);
p = lei.get(0);
lei.add(o);
lei.add(n);
lei.add(i);
lei.add(p);
lei.add(null);
CSE 331 Summer 2021
Legal operations on wildcard types
Object o;
Number n;
NewInteger i;
PositiveInteger p;
List<? extends NewInteger> lei;
First, which of these is legal?
lei = new ArrayList<Object>();
lei = new ArrayList<Number>();
lei = new ArrayList<NewInteger>();
lei = new ArrayList<PositiveInteger>();
lei = new ArrayList<NegativeInteger>();
Which of these is legal?
o = lei.get(0);
n = lei.get(0);
i = lei.get(0);
p = lei.get(0);
lei.add(o);
lei.add(n);
lei.add(i);
lei.add(p);
lei.add(null);
CSE 331 Summer 2021
Legal operations on wildcard types
Object o;
Number n;
NewInteger i;
PositiveInteger p;
List<? super NewInteger> lsi;
First, which of these is legal?
lsi = new ArrayList<Object>();
lsi = new ArrayList<Number>();
lsi = new ArrayList<NewInteger>();
lsi = new ArrayList<PositiveInteger>();
lsi = new ArrayList<NegativeInteger>();
CSE 331 Summer 2021
Legal operations on wildcard types
Object o;
Number n;
NewInteger i;
PositiveInteger p;
List<? super NewInteger> lsi;
First, which of these is legal?
lsi = new ArrayList<Object>();
lsi = new ArrayList<Number>();
lsi = new ArrayList<NewInteger>();
lsi = new ArrayList<PositiveInteger>();
lsi = new ArrayList<NegativeInteger>();
CSE 331 Summer 2021
Legal operations on wildcard types
Object o;
Number n;
NewInteger i;
PositiveInteger p;
List<? super NewInteger> lsi;
First, which of these is legal?
lsi = new ArrayList<Object>();
lsi = new ArrayList<Number>();
lsi = new ArrayList<NewInteger>();
lsi = new ArrayList<PositiveInteger>();
lsi = new ArrayList<NegativeInteger>();
Which of these is legal?
lsi.add(o);
lsi.add(n);
lsi.add(i);
lsi.add(p);
lsi.add(null);
CSE 331 Summer 2021
Legal operations on wildcard types
Object o;
Number n;
NewInteger i;
PositiveInteger p;
List<? super NewInteger> lsi;
First, which of these is legal?
lsi = new ArrayList<Object>();
lsi = new ArrayList<Number>();
lsi = new ArrayList<NewInteger>();
lsi = new ArrayList<PositiveInteger>();
lsi = new ArrayList<NegativeInteger>();
Which of these is legal?
lsi.add(o);
lsi.add(n);
lsi.add(i);
lsi.add(p);
lsi.add(null);
CSE 331 Summer 2021
Legal operations on wildcard types
Object o;
Number n;
NewInteger i;
PositiveInteger p;
List<? super NewInteger> lsi;
First, which of these is legal?
lsi = new ArrayList<Object>();
lsi = new ArrayList<Number>();
lsi = new ArrayList<NewInteger>();
lsi = new ArrayList<PositiveInteger>();
lsi = new ArrayList<NegativeInteger>();
Which of these is legal?
lsi.add(o);
lsi.add(n);
lsi.add(i);
lsi.add(p);
lsi.add(null);
o = lsi.get(0);
n = lsi.get(0);
i = lsi.get(0);
p = lsi.get(0);
CSE 331 Summer 2021
Legal operations on wildcard types
Object o;
Number n;
NewInteger i;
PositiveInteger p;
List<? super NewInteger> lsi;
First, which of these is legal?
lsi = new ArrayList<Object>();
lsi = new ArrayList<Number>();
lsi = new ArrayList<NewInteger>();
lsi = new ArrayList<PositiveInteger>();
lsi = new ArrayList<NegativeInteger>();
Which of these is legal?
lsi.add(o);
lsi.add(n);
lsi.add(i);
lsi.add(p);
lsi.add(null);
o = lsi.get(0);
n = lsi.get(0);
i = lsi.get(0);
p = lsi.get(0);
CSE 331 Summer 2021
Where are we?
CSE 331 Summer 2021
Java arrays
We know how to use arrays:
Java included the syntax above because it’s common and concise
But can reason about how it should work the same as this:
class Array<T> {
public T get(int i) { … “magic” … }
public T set(T newVal, int i) {… “magic” …}
}
So: If Type1 is a subtype of Type2, how should �Type1[] and Type2[] be related??
CSE 331 Summer 2021
Java Arrays
CSE 331 Summer 2021
Surprise!
CSE 331 Summer 2021
What can happen: the good
Programmers can use this subtyping to “do okay stuff”
void maybeSwap(LibraryHolding[] arr) {
if(arr[17].dueDate() < arr[34].dueDate())
// … swap arr[17] and arr[34]
}
// client with subtype
Book[] books = …;
maybeSwap(books); // relies on covariant
// array subtyping
CSE 331 Summer 2021
LibraryHolding
Book
CD
What can happen: the bad
Something in here must go wrong!
void replace17(LibraryHolding[] arr,
LibraryHolding h) {
arr[17] = h;
}
// client with subtype
Book[] books = …;
LibraryHolding theWall = new CD("Pink Floyd",
"The Wall", …);
replace17(books, theWall);
Book b = books[17]; // would hold a CD
b.getChapters(); // so this would fail
CSE 331 Summer 2021
LibraryHolding
Book
CD
Java’s choice
CSE 331 Summer 2021
Where are we?
CSE 331 Summer 2021
Type erasure
All generic types become type Object once compiled
List<String> lst = new ArrayList<String>();
at runtime, becomes
List<Object> lst = new ArrayList<Object>();
CSE 331 Summer 2021
Demo
73
Type erasure
All generic types become type Object once compiled
Cannot use instanceof to discover a type parameter
Collection<?> cs = new ArrayList<String>();
if (cs instanceof Collection<String>) { // illegal
...
}
CSE 331 Summer 2021
Generics and casting
Casting to generic type results in an important warning
List<?> lg = new ArrayList<String>(); // ok
List<String> ls = (List<String>) lg; // warn
Compiler gives a warning because this is something the runtime system will not check for you
Usually, if you think you need to do this, you're wrong
Object can also be cast to any generic type ☹
public static <T> T badCast(T t, Object o) {
return (T) o; // unchecked warning
}
CSE 331 Summer 2021
The bottom-line
CSE 331 Summer 2021
Recall equals
class Node {
…
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Node)) {
return false;
}
Node n = (Node) obj;
return this.data().equals(n.data());
}
…
}
CSE 331 Summer 2021
equals for a parameterized class
class Node<E> {
…
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Node<E>)) {
return false;
}
Node<E> n = (Node<E>) obj;
return this.data().equals(n.data());
}
…
}
CSE 331 Summer 2021
Erasure: Type arguments do not exist at runtime
equals for a parameterized class
class Node<E> {
…
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Node<?>)) {
return false;
}
Node<E> n = (Node<E>) obj;
return this.data().equals(n.data());
}
…
}
CSE 331 Summer 2021
More erasure: At run time, do not know what E is and will not be checked, so don’t indicate otherwise
equals for a parameterized class
class Node<E> {
…
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Node<?>)) {
return false;
}
Node<?> n = (Node<?>) obj;
return this.data().equals(n.data());
}
…
}
CSE 331 Summer 2021
Works if the type of obj is Node<Elephant> or Node<String> or …
Node<Elephant>
Node<String>
Node<? extends Object>
Leave it to here to “do the right thing” if this and n differ on element type
Generics and arrays
public class Foo<T> {
private T aField; // ok
private T[] anArray; // ok
public Foo() {
aField = new T(); // compile-time error
anArray = new T[10]; // compile-time error
}
}
CSE 331 Summer 2021
Necessary array cast
public class Foo<T> {
private T aField;
private T[] anArray;
@SuppressWarnings("unchecked")
public Foo(T param) {
aField = param;
anArray = (T[]) new Object[10];
}
}
You can declare variables of type T, accept them as parameters, return them, or create arrays by casting Object[]
CSE 331 Summer 2021
FINAL THOUGHTS
83
Generics clarify your code
interface Map {
Object put(Object key, Object value);
…
}
interface Map<Key,Value> {
Value put(Key key, Value value);
…
}
CSE 331 Summer 2021
plus casts in client code
→ possibility of run-time errors
Tips when writing a generic class
CSE 331 Summer 2021