OCP Review 3.2 - Generics
Generics
Problem they solve
- same operations with different data types
interface Stackable {
void push(String element);
String pop();
String peek();
int size();
}
class Stack implements Stackable {
private List<String> stack = new ArrayList<>();
@Override public void push(String element) {
stack.add(element);
}
@Override public String pop() {
if (stack.size() == 0) {
return null;
}
String topElement = peek();
stack.remove(stack.size() - 1);
return topElement;
}
@Override public String peek() {
if (stack.size() == 0) {
return null;
}
String topElement = stack.get(stack.size() - 1);
return topElement;
}
@Override public int size() {
return stack.size();
}
}
Problem they solve: same operations with different data types
- now we want a Stack of numbers, of People…
interface Stackable<T> {
void push(T element);
T pop();
T peek();
int size();
}
class Stack<T> implements Stackable<T> {
private List<T> stack = new ArrayList<>();
@Override public void push(T element) {
stack.add(element);
}
@Override public T pop() {
if (stack.size() == 0) {
return null;
}
T topElement = peek();
stack.remove(stack.size() - 1);
return topElement;
}
@Override public T peek() {
if (stack.size() == 0) { return null; }
T topElement = stack.get(stack.size() - 1);
return topElement;
}
@Override public int size() {
return stack.size();
}
}
Naming conventions
- T generic data type
- S, U, V: more types
- K: key
- V: value
- E: element
- N: number
Generic types / parametrized types
- generic type (when defining the generic type)
interface Collection<E> {
public void add (E x);
public Iterator<E> iterator();
}
- parametrized type (when using the generic type)
Collection<String> c = new LinkedList<String>();
Where can I apply generics?
- All types, except enum types, anonymous inner classes and exception classes, can be generic
enum Months<T> { // Compilation error: Enum may not have type parameters
}
Mixing raw types with Generics
Stack<Integer> numbers = new Stack<>();
numbers.push(10); // method has Integer in signature
Stack numbers = new Stack<Integer>();
numbers.push(10); // method has Object in signature, also warning
Raw type
- we can use generics to explicitly state a “list of whatever”
List<Object> objects = new LinkedList<>();
objects.add("Hello");
objects.add(1);
Raw type warning
- equivalent to
List objects = new LinkedList();
List objects = new LinkedList<>();
objects.add("Hello");
objects.add(1);
- we get a warning
Information:java: Main.java uses unchecked or unsafe operations.
Information:java: Recompile with -Xlint:unchecked for details.
Type erasure
- Generics only for compile time
- all types changed into
Object
, casts added by compiler - using Raw types: only using Object
// nice typed List
List<String> names = Arrays.asList("groucho", "harpo", "chicco");
for (String s: names) {
System.out.println(s);
}
// after type erasure
// converted into raw list
List<Object> oNames = Arrays.asList("groucho", "harpo", "chicco");
for (Object o: oNames) {
System.out.println((String)o);
}
Type erasure
System.out.println("runtime type of ArrayList<String>: "+new ArrayList<String>().getClass());
System.out.println("runtime type of ArrayList<Long> : "+new ArrayList<Long>().getClass());
prints:
runtime type of ArrayList<String>: class java.util.ArrayList
runtime type of ArrayList<Long> : class java.util.ArrayList
Type erasure consecuences
- problem: we can’t assign a
List<String>
to aList<Object>
Object
is a supertype ofString
->Object[]
supertype ofString[]
== covariant types- Parameterized types are not covariant
List<Object>
NOT supertype ofList<String>
List<String> names = Arrays.asList("groucho", "harpo", "chicco");
for (String s: names) {
System.out.println(s);
}
List<Object> objects = names; // COMPILATION ERROR
- if we could assign it, once asigned we don’t know what’s inside
List<String> names = Arrays.asList("groucho", "harpo", "chicco");
for (String s: names) {
System.out.println(s);
}
List<Object> objects = names; // COMPILATION ERROR: to protect us
objects.add("Hello"); // although we're treating objects as List<Object> it really is List<String>
objects.add(1); // BOOM
Type erasure consecuences
- we can assign to a List<?>
- problem: we can’t add anything to objects (inmutable list)
List<String> names = Arrays.asList("groucho", "harpo", "chicco");
for (String s: names) {
System.out.println(s);
}
List<?> objects = names;
for (Object s: objects) {
System.out.println(s);
}
Generic methods
- we can use generic types in just one method (not in the whole class)
- to limit the type to that method
- no need to create an object of MyClass using <>
List<String> ls = new ArrayList<>();
m(ls); // OK
List<Integer> ls2 = new ArrayList<>();
m(ls2); // NO OK: List<Integer> is not a List<String>
class MyClass {
static <T extends List<String>> void m(T a) {
a.clear();
}
}
Generic methods
- what if we want to clear any list
- and we’re not interested in the type of the list’s elements
static <T extends List<?>> void m(T a) {
a.clear();
}
Type parameter bound
- Type parameters can be declared with bounds
- Bounds give access to methods of the unknown type
- only
T extends Type
–> only upper bound - can’t do
T super Type
class OK<T extends String> {
}
class NO_OK<T super String> { // this doesn't exists
}
Type parameter bound
- can’t create new objects of the type parameter: I don’t know if this constructor is even available
T t = new T(); // wrong: is this constructor visible?
Type parameter bound: multiple types
class A<T extends Object & Runnable> { // extends Class & Interface & Interface
void m(T t) {
t.run();
}
}
class B<T extends List & Serializable> { // extends [Object] & Interface & Interface
void m(T t) {
t.clear();
}
}
Type parameter bound
- Every type variable declared as a type parameter has a bound. If no bound is declared for a type variable,
Object
is assumed
class OK<T> { // T is Object
}
Wildcards
- unbounded wildcard: ?
- unknown generic type
- upper bound: ? extends Class
- lower bound: ? super Class
Wildcard: ?
- problem: print all elements of a list, don’t care about type
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
debugPrintAnyList(stringList);
List<Integer> integerList = new ArrayList<>();
integerList.add(10);
integerList.add(20);
debugPrintAnyList(integerList);
}
public static void debugPrintAnyList(List<?> list) {
list.forEach(System.out::println);
}
Example classes for wildcards:
/*
-------------
| A |
-------------
^
|
-------------
| B |
-------------
^
|
-------------
| C |
-------------
*/
class A {
}
class B extends A {
}
class C extends B {
}
Upper-bound wildcard: ? extends Class
public static void debugPrint(List<? extends A> list) {
list.forEach(System.out::println);
}
List<A> aList = new ArrayList<>(); // works: A extends A
debugPrint(aList);
List<B> aList = new ArrayList<>(); // works: B extends A
Lower-bound: ? super Class
public static void debugPrint(List<? super B> list) {
list.forEach(System.out::println);
}
List<A> aList = new ArrayList<>(); // works: A is a superclass of B
debugPrint(aList);
Functional interfaces using generics
class Calculator<N extends Number> {
// can't create static variables as generic type parameters: the type is linked to the instance of the class
N calculate(N n1, N n2, Operation<N> op) {
return op.operate(n1, n2);
}
}
@FunctionalInterface
interface Operation<N> {
N operate(N n1, N n2);
}
Calculator<Integer> ci = new Calculator<>();
ci.calculate(1, 3, (n1, n2) -> n1 + n2);
Calculator<Float> cf = new Calculator<>();
cf.calculate(1.4f, 4.3f, (n1, n2) -> n1 - n2);
References
- Angelika Langer http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html#FAQ203
Questions
-
List<? extends Number> - A List containing instances of Number or its subclass(es). This will allow you to retrieve Number objects because the compiler knows that this list contains objects that can be assigned to a variable of class Number. However, you cannot add any object to the list because the compiler doesn’t know the exact class of objects contained by the list so it cannot check whether whatever you are adding is eligible to be added to the list or not.
-
Map<String , List<? extends CharSequence» stateCitiesMap = new HashMap<>(); use the diamond operators correctly to indicate the type of objects stored in the HashMap to the compiler. The compiler inferences the type of the objects stored in the map using this information and uses it to prevent the code from adding objects of another type.