Generics

Generic mechanism allows to write code without bonding to concrete types. This is also called metaprogramming.

Good example is any collection. Without generics we must create something MyListOfPersons, MyListOfShapes and etc. All these classes will have same logic. Yes, we can use the Object class to handle any type, but in some cases it will be unsafe. With generics we can write a MyList class with the type parameter.

List of the parameter types is specifies in angle brackets <> after the type name for the generic classes or interfaces. And before the return type for generic methods. Angle brackets are also called the diamond operator.

The extends keyword in the diamond operator allows you to specify the base class of the type parameter.

The type parameters can be omitted when they are well known, for example, when a variable is initialized. The type parameter can be parameterized type like List<String>.

Generic class or interface without any type arguments is called a raw type. In this case implicity the Object class will be used.

public class Demo<T extends Shape, T2>  { // declare generic class
    private T shape;
    private T2 field1;
  
    public void setShape(T shape) { this.shape=shape; }
    
    public class DemoNested  {
        private T item2;
    }
}

// declare generic method
public static <T> void swap(List<T> src, int ind1, int ind2 ){
    T temp =  src.get(ind1);
    src.set(ind1, src.get(ind2));
    src.set(ind2, temp);
}

public class Demo<T> { /*...*/ } Demo<Shape> demoShape = new Demo<>(); // empty <> Demo<List<String>> lstStringDemo = new Demo<>(); // parameterized type is used Demo demo = new Demo(); // raw type same as Demo<Object> demo = demoShape; // allowed ArrayList<String> lst = new ArrayList<>(); lst.add("str0"); lst.add("str1"); swap(lst, 0,1); // result is "el0: str1 el1: str0" System.out.println("el0: "+lst.get(0)+ " el1: "+lst.get(1));

wildcard

In generic code, the question mark ?, called the wildcard, represents an unknown type. This is useful when the code is independent of the type parameter.

public static void printAll(List<?> src ){
    for(Object el: src){
        System.out.println(el+" ");
    }
}
// from java code
public static void swap(List<?> list, int i, int j) {
    final List l = list;
    l.set(i, l.set(j, l.get(i)));
}

Don't confuse List<Object> and List<?>, they are not the same. You can insert an Object, or any subtype of Object, into a List<Object>. But you can only insert null into a List<?>.

upper-bounded wildcards

Willcard followed by the extends keyword with the name of base class is called upper-bounded wildcard. This means that the type parameter can be any type that inherit from the specified type.

In example below, you can read items as Number from foo. But we can not read as Integer because foo can point to List<Double>. And we cannot read as Double because foo can point to List<Integer>

Similarly, we can not add any item because we don't know real type of foo.

// upper-bounded
List<? extends Number> foo = new ArrayList<Integer>(); // Integer extends Number
foo = new ArrayList<Double>();  // Double extends Number
foo = new ArrayList<Number>();  // also allowed

lower-bounded wildcards

Willcard followed by the super keyword with the name of base class is called lower-bounded wildcard. This means that the type parameter can be any type that superclass of the specified type.

In example below, you can read items as Object from foo because Object is a base class for all classes in Java. But we can not read as Integer or Double because foo can point to List<Object>.

You can add element of specified type or its subclasses (Integer in our example). But not Number, Object or Double, because foo can point to List<Integer>

// lower-bounded
List<? super Integer> foo3 = new ArrayList<Number>();   // Number is a superclass of Integer
foo = new ArrayList<Object>();   // Object is a superclass of Integer
foo = new ArrayList<Integer>();  // also allowed

producer-consumer pattern

Bounded wildcards is useful in producer-consumer pattern. Upper-bounded is used for producer and lower-bounded for consumer.
// you can see Collections.copy for full code
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (int i = 0; i < src.size(); ++i) { 
        dest.set(i, src.get(i)); 
    }
} 

type parameter naming conventions

By convention, type parameter names are single, uppercase letters. The most commonly used type parameter names are:

  • E - Element
  • K - Key
  • N - Number
  • T - Type
  • V - Value