- Runtime bugs are difficult to trace back to their roots because they may surface at a point in a program far removed from their origin.
- Types (classes and interfaces) are parameterized during class definition, interface definition or method definition.
- Inputs to type parameters are types
- Advantages of generic code:
- Stronger type checking at compile time
- Fixing compile time errors is easier than fixing run-time errors
- Cast elimination
-
Code without generics requires casting:
List list = new ArrayList(); list.add("hello"); String s = (String) list.get(0); -
Generic code does not require casting:
List<String> list = new ArrayList<String>(); list.add("hello"); String s = list.get(0); // no cast -
Enables a programmer to implement generic algorithms
- generic algorithms work on different collections of different types
-
- Stronger type checking at compile time
-
Simple Box class
-
Non generic box class
public class Box { private Object object; public void set(Object object) { this.object = object; } public Object get() { return object; } } -
We can pass in any object/ array
-
Problem:
- One part of code may place Integer in box and expects to get Integers
- Another part of code may pass String
-
-
A generic Version of the Box Class
-
Syntax:
class name<T1, T2, ..., Tn> { /* ... */ }- T1, T2, ..., Tn are type parameters
-
Generic Box class
/** * Generic version of the Box class. * @param <T> the type of the value being boxed * / public class Box<T> { // T stands for "Type" private T t; public void set(T t) { this.t = t; } public T get() { return t; } }- Type variable can be:
- class type
- interface type
- array type
- another type variable (?)
- Type variable can be:
-
-
Type Parameter Naming Conventions
- Single uppercase letters
- Common type parameters
- E - Element (Java Collection framework)
- K - key
- N - Number
- T - Type
- V - Value
- S, U, V ... - 2nd, 3rd, 4th types
-
Invoking and Instantiating a Generic Type
- Replace generic type with concrete type during invocation
Box<Integer> integerBox; // integerBox will hold a reference to a Box of Integer
- Instantiation
Box<Integer> integerBox = new Box<Integer>();
- Replace generic type with concrete type during invocation
-
The Diamond
- In Java SE 7 and later, can use <> during constructor invocation of a generic type if compiler can determine the type argument(s) without ambiguity from its context.
Box<Integer> integerBox = new Box<>();
- In Java SE 7 and later, can use <> during constructor invocation of a generic type if compiler can determine the type argument(s) without ambiguity from its context.
-
Multiple Type Parameters
-
Example:
public interface Pair<K, V> { public K getKey(); public V getValue(); } public class OrderedPair<K, V> implements Pair<K, V> { private K key; private V value; public OrderedPair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } }-
Instantiation
Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8); Pair<String, String> p2 = new OrderedPair<String, String>("hello", "world"); -
Instantiation using diamond notation:
OrderedPair<String, Integer> p1 = new OrderedPair<>("Even", 8); OrderedPair<String, String> p2 = new OrderedPair<>("hello", "world");
-
-
-
Parameterized Types
-
Example:
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));
-
-
Raw type: name of generic class without arguments:
Box rawBox = new Box(); // Box is the raw type of generic type Box<T>
-
Gives rise to pre-generics behavior.
-
Assignment of parametereized type object to it's raw type reference is allowed for backwards compatibility.
Box<String> stringBox = new Box<>(); Box rawBox = stringBox; -
If a raw type object is assigned to parameterized type reference, a warning is shown
Box rawBox = new Box(); // rawBox is a raw type of Box<T> Box<Integer> intBox = rawBox; // warning: unchecked conversion -
If a rawType object is used to invoke a generic method defined in corresponding generic type, a warning will be shown
Box<String> stringBox = new Box<>(); Box rawBox = stringBox; rawBox.set(8); // warning: unchecked invocation to set<T> -
raw types defer catching unsafe code to runtime.
-
unchecked: compiler does not have enough information to perform all type checks to ensure safety.
-
Recompile with
-xlint:uncheckedwill give additional information about the warning -
How to suppress unchecked warnings:
- compile with:
-Xlint:-unchecked - Use
@SuppressWarnings("unchecked")
- compile with:
-
Methods with type parameters.
-
Type parameters scope is limited to the method of declaration.
-
Methods supported:
- Static methods.
- non-static methods
- generic class constructors
-
Syntax: declaration appears before method's return type (for static methods it is a must)
public class Util { public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) { return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue()); } } public class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } public K getKey() { return key; } public V getValue() { return value; } }-
Invocation
Pair<Integer, String> p1 = new Pair<>(1, "apple"); Pair<Integer, String> p2 = new Pair<>(2, "pear"); boolean same = Util.<Integer, String>compare(p1, p2); **(M)** -
Invocation with type inference (invocation without explicitly specifying type between angle brackets)
Pair<Integer, String> p1 = new Pair<>(1, "apple"); Pair<Integer, String> p2 = new Pair<>(2, "pear"); boolean same = Util.compare(p1, p2);
-
- To restrict type arguments to fewer types.
- Example: To pass only types which are subclasses of
Number
- Example: To pass only types which are subclasses of
- Syntax:
<U extends Number>(both for Class and Interface types)-
Numberis upper-bound -
Example:
public class Box<T> { private T t; public void set(T t) { this.t = t; } public T get() { return t; } public <U extends Number> void inspect(U u) { System.out.println("T: " + t.getClass().getName()); System.out.println("U: " + u.getClass().getName()); } public static void main(String[] args) { Box<Integer> integerBox = new Box<Integer>(); integerBox.set(new Integer(10)); integerBox.inspect("some text"); // error: this is still String! } } -
One can invoke methods defined in the bounded types
public class NaturalNumber<T extends Integer> { private T n; public NaturalNumber(T n) { this.n = n; } public boolean isEven() { return n.intValue() % 2 == 0; } // ... } -
Multiple Bounds
-
Syntax:
<T extends B1 & B2 & B3>(M)- If a bound is a class then it must be specified first
-
Example:
class A { /* ... */ } interface B { /* ... */ } interface C { /* ... */ } class D <T extends A & B & C> { /* ... */ }
-
-
-
Example:
public interface Comparable<T> { public int compareTo(T o); } public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) { int count = 0; for (T e: anArray) { if (e.compaterTo(elem) > 0) ++count; } } }
-
We can invoke generic type by passing a supertype as its type argument and subsequent method calls can accept subtype type objects.
-
Example:
Box<Number> box = new Box<Number>(); box.add(new Integer(10)); // ok box.add(new Double(10.1)); -
Box<Integer>andBox<Double>are not subtypes ofBox<Number>and hence the following code will not acceptBox<Number>orBox<Double>type objectspublic void boxText(Box<Number> n) { /* ... */ }
-
Generic Classes and Subtyping
-
Generic class can be subtyped by extending or implementing it.
-
Example:
ArrayList<E>implementsList<E>andList<E>extendsCollection<E>
-
Type argument must be the same for subtype relationships
-
Example:
-
PayloadList<E, P>extendsList<E>and associates a value of typePwith each element of typeEinterface PayloadList<E, P> extends List<E> { void setPayload(int index, P val); ... } -
PayloadList<String, String>,PayloadList<String, Number>andPayloadList<String, Exception>are subtypes ofList<String>
-
-
- Java compiler looks at method invocation and corresponding method declaration to determine type argument to make the invocation.
- Inference algorithm determines types of arguments, type of result assigned or returned.
- Inference algorithm finds most specific type that works with all arguments
-
Example:
static <T> T pick(T a1, T a2) { return a2; } Serializable s = pick("d", new ArrayList<String>()); // compiler identifies the second argument as Serializable object -
Type Inference and Generic Methods
-
Type arguments need not be passed to generic methods explicitly using angle brackets.
-
Example:
public class BoxDemo { public static <U> void addBox(U u, java.util.List<Box<U>> boxes) { Box<U> box = new Box<>(); box.set(u); boxes.add(box); } public static <U> void outputBoxes(java.util.List<Box<U>> boxes) { int counter = 0; for (Box<U> box: boxes) { U boxContents = box.get(); System.out.println("Box #" + counter + " coutains [" + boxContents.toString() + "]"); counter++; } } public static void main(String[] args) { java.util.ArrayList<Box<Integer>> listOfIntegerBoxes = new java.util.ArrayList<>(); BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes); BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes); BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes); BoxDemo.outputBoxes(listOfIntegerBoxes); } }- To invoke generic method
addBoxwe can specify type parameter as follows:BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
- If we ignore the type witness, java will automatically infer the type
IntegerBoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
- To invoke generic method
-
-
Type Inference and Instantiation of Generic Classes
- Type arguments while invoking the constructor can be ignored using
<>diamondMap<String, List<String>> myMap = new HashMap<String, List<String>>();can be re-written asMap<String, List<String>> myMap = new HashMap<>();
- Type arguments while invoking the constructor can be ignored using
-
Type Inference and Generic Constructors of Generic and Non-Generic Classes
-
Constructors can be generic declaring their own type parameters in both generic and non-generic classes
-
Example:
class MyClass<X> { <T> MyClass(T t) { // ... } } new MyClass<Integer>(""); // compiler infers the type of T as String because String type object is passed as argument -
Example 2:
MyClass<Integer> myObject = new MyClass<>(""); // compiler infers the type Integer for X and the type String for T -
Inference algorithm does not use results from later in the program for inference
-
-
Target Types
-
Target type is the datatype of an expression.
-
Example:
static <T> List<T> emptyList();- Assignment statement:
List<String> listOne = Collections.emptyList(); // compiler will infer type of T as String - Alternative assignment statement:
List<String> listOne = Collections.<String>emptyList();
- Assignment statement:
-
Example 2: Consider the following method:
void processStringList(List<String> stringList) { // process stringList }- Java SE 7 does not support the following:
processStringList(Collections.emptyList()); // List<Object> cannot be converted to List<String> error- Compiler looks for an object type for T and assigns
Object
- Compiler looks for an object type for T and assigns
- Java SE 8 supports the above invocation (target type is expanded to include method arguments)
- Java SE 7 does not support the following:
-
-
- ?: wildcard - unknown type
- wildcard can be used as
- type of parameter
- field
- local variable
- return type
- Wildcard is not used as
- type argument for generic method invocation, generic class instance creation, supertype
- Used to relax restrictions on a variable
- Example:
- The upper bound for
List<Integer>,List<Double>andList<Number>isNumber
- The upper bound for
- Syntax:
? extends <upper-bound>(for both classes and interfaces)- Example:
- For
List<Integer>,List<Double>andList<Number>useList<? extends Number>(match list of anyNumber)
- For
- Example:
-
public static void process(List<? extends Foo> list) { /* ... */ } -
Access list elements as of type
Foo:public static void process(List<? extends Foo> list) { for (Foo elem: list) { // ... } } -
We can use any method defined in
Fooclass
-
- Example 2:
-
sumOfListto return sum of numberspublic static double sumOfList(List<? extends Number> list) { double s = 0.0; for (Number n: list) { s += n.doubleValue(); } } List<Integer> li = Arrays.asList(1, 2, 4); System.out.println("sum = " + sumOfList(li)); List<Double> ld = Arrays.asList(2.3, 4.6, 7.2); System.out.println("sum = " + sumOfList(ld));
-
-
? is used to specify unbounded type
-
Example:
List<?>- list of unknown type
-
Purpose:
- Writing method that can be implemented using functionality in
Objectclass - If code is using methods in generic class that do not require type parameter:
List.size,List.clear - Example:
Class<?>is used because most methods inClass<T>do not use type parameter.
- Writing method that can be implemented using functionality in
-
Example:
public static void printList(List<?> list) { for (Object elem: list) { System.out.println(elem + " "); } System.out.println(); } List<Integer> li = Arrays.asList(1, 2, 3); List<String> ls = Arrays.asList("one", "two", "three"); // **(M)** static factory method converts array to fixed-size list printList(li); printList(ls);
-
LBW restricts unknown type to specifc type or a super type of the specific type
-
Syntax:
<? super A>(M)
-
We cannot specify both lower bound and upper bound for a wildcard
-
Example: A method puts
Integerobjects into a list.public static void addNumbers(List<? super Integer> list) { for(int i = 1; i <= 10; i++) { list.add(i); } }
- Common parent of
List<Number>andList<Integer>isList<?>
-
List<? extends Integer>is a subtype ofList<? extends Number>List<? extends Integer> intList = new ArrayList<>(); List<? extends Number> numList = intList;
-
Wildcard capture: In certain scenarios, compiler infers the type of a wildcard.
- Example: Consider
List<?>. When evaluating an expression, compiler infers a type for?
- Example: Consider
-
Handle it when you see the error "capture of"
-
Example:
import java.util.List; public class WildcardError { void foo(List<?> i) { i.set(0, i.get(0)); } } -
Compiler infers the type of
iinput parameter to beObject -
Compiler cannot confirm the type of object when
List.set(int, E)is invoked. Compiler assumes wrong type variable assignment.
-
-
Solution:
-
Use private helper method which captures the wildcard.
public class WildCardFixed { void foo(List<?> i) { fooHelper(i); } // Helper method created so that the wildcard can be captures // through type inference private <T> void fooHelper(List<T> l) { l.set(0, l.get(0)); } }
-
-
Convention for helper method: originalMethodNameHelper
-
Example of code for which there is no helper method
import java.util.List; public class WildcardErrorBad { void swapFirst(List<? extends Number> l1, List<? extends Number> l2) { Number temp = l1.get(0); l1.set(0, l2.get(0)); // expected a CAP#1 extends Number, // got a CAP#2 extends Number; // same bound, but different types l2.set(0, temp); // expected a CAP#1 extends Number, // got a Number } } List<Integer> li = Arrays.asList(1, 2, 3); List<Double> ld = Arrays.asList(10.10, 20.20, 30.30); swapFirst(li, ld);
- When to use upper bounded and lower bounded wild cards?
- Variables provide two functions
- "In" variable: serves data to code. Example:
copy(src, dest);-srcis "In" parameter - "Out" variable: holds data for use elsewhere. Example:
dest
- "In" variable: serves data to code. Example:
- Some variables server both functions of "In" and "Out".
- Variables provide two functions
- Guidelines (not for method's return type):
- "In" variable is defined with upper bound wildcard, using
extends - "Out" variable is defined with lower bound wildcard, using
super - If "In" variable can be accessed using methods of
Objectclass, use unbounded wildcard - If variable acts as both "In" and "Out" variable, then do not use a wildcard
- Do not use wildcards for return type (programmers have to deal with it (?))
- "In" variable is defined with upper bound wildcard, using
List<? extends ...>is read-only informally (cannot add or change an element but can remove elements or addnulland capture and add elements read from the list).-
Example:
class NaturalNumber { private int i; public NaturalNumber(int i) { this.i = i; } } class EvenNumber extends NaturalNumber { public EvenNumber(int i) { super(i); } } List<EvenNumber> le = new ArrayList<>(); List<? extends NaturalNumber> ln = le; ln.add(new NaturalNumber(35)); // compile-time error ln.add(new EvenNumber(36)); // compile-time error -
Possible operations
-
can add
nullln.add(null);
-
can invoke
clearln.clear();
-
can get iterator and invoke remove
Iterator i = ln.getIterator(); i.remove(e); -
Can capture wildcard and write elements you have read from the list
-
-
- For generics implementation, Java compiler applies type erasure to:
- If type parameters are unbounded, they are replaced with their bounds or
Object - Insert type casts if necessary to preserve type safety
- In extended generic types, bridge methods are generated to preserve polymorphism
- If type parameters are unbounded, they are replaced with their bounds or
- Note: No new classes are created
-
If type parameter is bounded: compiler replaces it with the first bound
-
If type parameter is unbounded: compiler replaces it with
Objectpublic class Node<T> { private T data; private Node<T> next; public Node(T data, Node<T> next) { this.data = data; this.next = next; } public T getData() { return data; } // ... }-
Java compiler replaces
TwithObjectpublic class Node { private Object data; private Node next;
public Node(Object data, Node next) { this.data = data; this.next = next; } public Object getData() { return data; } // ...}
-
-
Example of Bounded type parameter
public class Node<T extends Comparable<T>> { private T data; private Node<T> next; public Node(T data, Node<T> next) { this.data = data; this.next = next; } public T getData() { return data; } // ... }-
Java compiler replaces
Twith first bound classComparablepublic class Node { private Comparable data; private Node next; public Node(Comparable data, Node next) { this.data = data; this.next = next; } public Comparable getData() { return data; } // ... }
-
-
Example of generic method with unbounded type parameter:
// Counts the number of occurences of elem in anArray. // public static <T> int count(T[] anArray, T elem) { int cnt = 0; for (T e: anArray) { if (e.equals(elem)) ++cnt; } return cnt; }-
Java compiler replaces
TwithObjectpublic static int count(Object[] anArray, Object elem) { int cnt = 0; for (Object e: anArray) { if (e.equals(elem)) ++cnt; } return cnt; }
-
-
Example of a generic method with bounded type parameter:
class Shape { /* ... */ } class Circle extends Shape { /* ... */} class Rectangle extends Shape ( /* ... */ }-
If a generic method is defined to draw different
Shapespublic static <T extends Shape> void draw(T shape) { /* ... */ } -
Java compiler replaces
TwithShapepublic static void draw(Shape shape) { /* ... */ }
-
-
The following example shows how a bridge method gets created:
public class Node<T> { public T data; public Node(T data) { this.data = data; } public void setData(T data) { System.out.println("Node.setData"); this.data = data; } } public class MyNode extends Node<Integer> { public MyNode(Integer data) { super(data); } public void setData(Integer data) { System.out.println("MyNode.setData"); super.setData(data); } }-
Consider the following code:
MyNode mn = new MyNode(5); Node n = mn; n.setData("Hello"); Integer x = mn.data; -
Type erasure converts the above code to:
MyNode mn = new MyNode(5); Node n = (MyNode)mn; n.setData("Hello"); Integer x = (String)mn.data; // Causes a ClassCastException to be thrown- Code execution process:
n.setData("Hello");causesn.setData(Object)to be executed on the object of classMyNode- In the body of
setData(Object), data field of object referenced bynis assigned to aString - data field of the object referenced by
mnis expected to be anIntegerbecausemnis aNode<Integer> - If a
Stringis tried to be assigned to anIntegerwe getClassCastException
- Code execution process:
-
-
Bridge Methods
-
If a class or interface extends or implements a parameterized class or interface respectively, a synthetic method may be created called bridge method as part of type erasure process (bridge methods may appear in stack-trace)
-
NodeandMyNodeclasses after type erasurepublic class Node { public Object data; public Node(Object data) { this.data = data; } public void setData(Object data) { System.out.println("Node.setData"); this.data = data; } } public class MyNode extends Node { public MyNode(Integer data) { super(data); } public void setData(Integer data) { System.out.println("MyNode.setData"); super.setData(data); } }-
Method signatures of
setData(Object)andsetData(Integer)do not match ->MyNodesetDatadoes not overrideNodesetData -
Compiler preserved the polymorphism after type erasure by generating a bridge method for
MyNodeclassclass MyNode extends Node { // Bridge method generated by the compiler // public void setData(Object data) { setData((Integer)data); } public void setData(Integer data) { System.out.println("MyNode.setData"); super.setData(data); } // ... }
-
-




