Generic methods and generic classes enable programmers to specify, with a single method declaration, a set of related methods, or with a single class declaration, a set of related types, respectively. Additionally, you don’t have to cast the values you obtain from the collection.

Example :

List<String> strings = new ArrayList<String>();
strings.add("a String");
String aString = strings.get(0);

 


Generic Class

A generic type is a class or interface that is parameterized over types, using angle brackets (<>) to specify the type parameter.

// Generic class example
// <T> the type of the value being boxed
public class GenericsType<T> {

    // T stands for "Type"
    private T t;
    
    public T get(){
        return this.t;
    }
    
    public void set(T t1){
        this.t=t1;
    }
    
    public static void main(String args[]){
        
      // String type, which replaces T with 'String'  
      GenericsType<String> type = new GenericsType<>();
      type.set("JAVA"); //valid
      
      // Raw type
      GenericsType type1 = new GenericsType();
      type1.set("JAVA"); //valid
      type1.set(10);     //valid and autoboxing support
    }
}

 

In above example we don’t need to do type-casting and we can remove ClassCastException at runtime. If we don’t provide the type at the time of creation, compiler will produce a warning that “GenericsType is a raw type. References to generic type GenericsType<T> should be parameterized”. When we don’t provide type, the type becomes Object and hence it’s allowing both String and Integer objects but we should always try to avoid this because we will have to use type casting while working on raw type that can produce runtime errors.

Generic Type

Generic Type Naming convention helps us understanding code easily and having a naming convention is one of the best practices of java programming language. The most commonly used type parameter names are:

  • E – Element (used extensively by the Java Collections Framework, for example ArrayList, Set etc.)
  • K – Key (Used in Map)
  • N – Number
  • T – Type
  • V – Value (Used in Map)
  • S,U,V etc. – 2nd, 3rd, 4th types

 

Generic Method

Generics methods are similar to declaring a generic type, but the type parameter’s scope is limited to the method where it is declared. Static and non-static generic methods are allowed, as well as generic class constructors. Since constructor is a special kind of method, we can use generics type in constructors too.

// Generic method example
public class GenericsMethods {

  // Generic Method, syntax includes a list of type parameters, 
  // inside angle brackets, which appears before the method's return type
  public static <T> boolean isEqual(GenericsType<T> g1, GenericsType<T> g2){
    return g1.get().equals(g2.get());
  }
  
  public static void main(String args[]){
    // Class defined earlier
    GenericsType<String> g1 = new GenericsType<>();
    g1.set("India");
    
    GenericsType<String> g2 = new GenericsType<>();
    g2.set("USA");
    
    boolean isEqual = GenericsMethods.<String>isEqual(g1, g2);

    // Above statement can be written simply as
    isEqual = GenericsMethods.isEqual(g1, g2);
  }
}

 

Notice the isEqual method signature showing syntax to use generics type in methods. We can specify type while calling these methods or we can invoke them like a normal method.

 

Bounded Type Parameters

There may be times when you’ll want to restrict the kinds of types that are allowed to be passed to a type parameter. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses. This is what bounded type parameters are for.

To declare a bounded type parameter, list the type parameter’s name, followed by the extends keyword, followed by its upper bound.

public class MaximumTest {

  // Determines the largest of three Comparable objects
  public static <T extends Comparable<T>> T maximum(T x, T y, T z) {

    // Assume x is initially the largest
    T max = x;
     
    if (y.compareTo(max) > 0) {
       max = y;   // y is the largest so far
    }
    
    if(z.compareTo(max) > 0) {
       max = z;   // z is the largest now                 
    }

    // Returns the largest object   
    return max;
  }
  
  public static void main(String args[]) {
    System.out.printf("Max of %d, %d and %d is %d\n\n",
       3, 4, 5, maximum( 3, 4, 5 ));

    System.out.printf("Max of %s, %s and %s is %s\n","pear",
        "apple", "orange", maximum("pear", "apple", "orange"));
  }
}

 

Generics Wildcards

Generic’s wildcards is a mechanism in Java Generics aimed at making it possible to cast a collection of a certain class, e.g A, to a collection of a subclass or superclass of A. Question mark (?) is the wildcard in generics and represent an unknown type. The wildcard can be used as the type of a parameter, field, or local variable and sometimes as a return type. We can’t use wildcards while invoking a generic method or instantiating a generic class.

A list of Number can contain Integer and Double objects (two subtypes of Number), hence the following example:

List<Number> numbers = new ArrayList<Number>();

numbers.add(2016);  // auto-boxing converts primitive to new Integer(2016)
numbers.add(3.14);  // auto-boxing converts primitive to new Double(3.14)

Here substitution Principle is applied for the reference type (List) and object type (ArrayList). The add() method can accept any type which is subtype of Number, thus an Integer and a Double objects can be added to the collection.  However, the Substitution Principle does not work with the parameterized types,

List<Number> numbers = new ArrayList<Integer>(); // Illegal

That also means it’s illegal to pass a collection of a subtype to a method with a parameter of the supertype. The following example illustrates this rule well:

class Animal { }

class Dog extends Animal { }
 
class Test {
    public void addAnimal(List<Animal> animals) { }
 
    public void test() {
        List<Dog> dogs = new ArrayList<Dog>();
        addAnimal(dogs);    // COMPILE ERROR!
    }
}

 

The generic wildcard operator is a solution to the problem explained above. The generic wildcards target two primary needs:

  • Reading from a generic collection
  • Inserting into a generic collection

There are three ways to define a collection (variable) using generic wildcards. These are:

List<?>           listUknown = new ArrayList<A>(); // List type is unknown
List<? extends A> listUknown = new ArrayList<A>(); // Upper Bounded Wildcard
List<? super   A> listUknown = new ArrayList<A>();

 

Unknown Wildcard

List<?> means a list typed to an unknown type. Since the type of the List is unknown, you can only read from the collection, and you can only treat the objects read as being Object instances. Here is an example:

public void processElements(List<?> elements){
   for(Object o : elements){
      System.out.println(o);
   }
}

 

Upper Bounded Wildcard

List<? extends A> means a List of objects that are instances of the class A, or subclasses of A (e.g. B and C). Upper bounded wildcards are used to relax the restriction on the type of variable in a method. Suppose we want to write a method that will return the sum of numbers in the list, so our implementation will be something like this.

public static double sum(List<? extends Number> list){
  double sum = 0;
  for(Number n : list){
      sum += n.doubleValue();
  }
  
  return sum;
}

 

In above method we can use all the methods of upper bound class Number.

Lower bounded Wildcard

List<? super A> means that the list is typed to either the A class, or a superclass of A. When you know that the list is typed to either A, or a superclass of A, it is safe to insert instances of A or subclasses of A (e.g. B or C) into the list.

Suppose we want to add Integers to a list of integers in a method, we can keep the argument type as List<Integer> but it will be tied up with Integers whereas List<Number> and List<Object> can also hold integers, so we can use lower bound wildcard to achieve this. We use generics wildcard (?) with super keyword and lower bound class to achieve this.

We can pass lower bound or any super type of lower bound as an argument in this case, java compiler allows to add lower bound object types to the list.

public static void addIntegers(List<? super Integer> list){
  list.add(new Integer(50));
}