Skip to the content.

Generics

-

What we’ll cover

What is a Generic Class?

Defining a Generic Class

What you can't do with Generics

Bounds

Wildcards

Type Erasure

Polymorphism and Bridge Methods

Enums

Reflection and Why it works

-

Background

-

Generic examples we’ve seen before

-

Simple generic class example

-

Container without Generics

class Container {
  private Object element;

  public Object get(){return element;}

  public void put(Object item){ element = item; }
}

-

Now, when you need to use it, you get into situations like

Container container = new Container();
String hello = "Hello, World!"
// String s = container.get(); // This doesn't work
String s = (String) container.get(); //Must be cast

-

Defining a generic class

class Container<T> {
  private T element;

  public T get(){return element;}

  public void put(T item){ element = item; }
}

-

Now, no cast needed!

You can even use two types.

class Pair<T,U> {
  private T first;
  private U second;
  ...
}

- -

Type inference with the <> operator

Added in Java 7; makes your code more readable

Container<String> stringContainer = new Container<String>();
Container<String> stringContainer = new Container<>();
Map<String, List<Integer>> dbTableIndex = new HashMap<String, ArrayList<Integer>>();
Map<String, List<Integer>> dbTableIndex = new HashMap<>();

-

We can do methods, too.

class MiddleHelper {
  public static <T> T getMiddle(T... a) { return a[a.length /2 ]; }
}

Note, if you don’t specify the type when you call the method, you can get into scenarios where the compiler doesn’t understand what you’re trying to do.

double middle = MiddleHelper.getMiddle(3.14, 1729, 0);

-

What CAN’T we do with Generics

-

No primitive types

Use wrapper classes. E.g.:

// Instead of: ArrayList<char> charList;
ArrayList<Character> charList;
// Instead of: Map<int, double> testResults;
Map<Integer, Double> testResults;

-

No runtime type inquiry on inner types

-

No Arrays of parameterized types

-

No instantiating type variables

-

No generic arrays

-

No using them as statics members in generic classes.

public class <T> Thing{
  // Badbadbad
  public static T badGenericVariable; //ERROR ERROR ERROR!!!
  //Seriously, this won't compile.
  public static void main(String[] args){
    Thing<String> thingOne = new Thing<>();
    Thing<Integer> thingTwo = new Thing<>();
    //Which of these would be allowed?
    badGenericVariable = new Integer(42);
    badGenericVariable = "Won't compile!";
  }
}


-

No throwing or catching them.

-

Bounds

public static <T extends Comparable> T min(T[] a){...}

When extending T must always be a subtype of it’s bounding type.
REMEMBER: if B extends A, that does NOT mean that T<B> extends T<A> - -

Wildcards

Unbounded Wildcards are used when you don’t care about the type.

public static boolean isEven(List<?> list) {
  return (list.size() % 2 == 0)
}

Bounded Wildcards, however, let you care somewhat about the type
? extends Something is typically when you are reading from something generic. Means any subclass of Something.
? super Something is typically when you are writing to something generic. Means any superclass of Something.
- - You can also capture wildcards by passing the variable to another function that doesn’t have a wildcard. Though, this is rarely used (or allowed), since the compiler needs to be certain that the wildcard represents a single type. - -

Type Erasure

At compile time for generics, the type parameters are erased and replaced with their upper bounds. And, if there isn’t one, then it replaces them with Object.

class Box<T> {
  public T contents;
  public T getContents {return contents;}
}

becomes

class Box {
  public Object contents;
  public Object getContents {return contents;}
}

- - and

class Box<T extends Comparable> {
  public T contents;
  public T getContents {return contents;}
}

becomes

class Box{
  public Comparable contents;
  public Comparable getContents {return contents;}
}

Also, if Box extended multiple things, the compiler would merely make everything the first type, and then cast to the latter ones when necessary. - -

Polymorphism and Bridge Methods

Java synthesizes bridge methods for us so we can have generics and polymorphism. - -

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);
    }
}

- - After type erasure (note setData methods):

public 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); }
	 //Does not override Node.setData() !!!
    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

- - Compiler creates a new “Bridge method” to fix this problem

class 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);
    }

    // ...
}

-

Reflection

You can, in fact, leverage reflection to find out about an Object’s generic past. This is all at runtime, though. So, in all reality, you can see what’s happening and where Objects came from, but know that as far as the compiler is concerned, they don’t really matter. Like, they’ve functionally been erased, but there is still a record.