sveska

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 a List<Object>
  • Object is a supertype of String -> Object[] supertype of String[] == covariant types
  • Parameterized types are not covariant
  • List<Object> NOT supertype of List<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.