Generics

Generics allows us to write methods that can accept any type of data.

Let us check a normal example:

public class Test {
    public static void main(String[] args) {
       Data d = new Data(); 
       d.show(new int[]{4,5,6,7,8});
    }
}
class Data{
 
 public void show(int[] ar){
    for(int x : ar)
       System.out.print(x + " ");
    }
}

The problem with this example is that if we want to pass float array or a string array, we would need two more methods. We can use generics to solve this issue.

A generic class can be declared something like this:

class Data<T>{        /* to accept single data type */
}

or

class Data<T1, T2>{            /* to accept two data types */
}

Now, we can create object of this class like this:

Data<Integer> obj = new Data<Integer>();   /* we can use any type in place of Integer */

or from JDK1.6 and above:

Data<Integer> obj = new Data<>();

Here is an example that uses generics:

public class Test {
    public static void main(String[] args) {

       Data<Integer> objInt = new Data<Integer>();
       Data<Double> objDouble = new Data<Double>();
       Data<String> objString = new Data<String>();
 
       objInt.show(new Integer[]{4,5,6,7,8});
       objDouble.show(new Double[]{4.5,5.6,6.9,7.1,8.3});
       objString.show(new String[]{"first","second","third","fourth","fifth"});
    }
}
class Data<T>{
 
    public void show(T[] ar){
        for(T x : ar)
            System.out.print(x + " ");
 
        System.out.println();
    }
}

**Note: Generics works only with reference types and not value types ( so not int, float etc. , only their wrapper types )

Some common generic types from Java
  1. ArrayList / List
    ArrayList class represents dynamic arrays in Java. It gives following advantages over normal arrays:

    • We don’t have to specify the size of list initially
    • It grows automatically as we add more data
    • We can insert data anywhere in the list
    • We can remove data by index or by value


    ArrayList
    class implements the List interface. Here is an example of ArrayList in action:

    import java.util.ArrayList;
    
    public class Test {
        public static void main(String[] args) {
            ArrayList<Integer> numbers = new ArrayList<Integer>();
     
            numbers.add(30);
            numbers.add(60);
            numbers.add(40);
            numbers.add(10);
            numbers.add(20);
     
            numbers.add(2,100);
     
            numbers.remove(0); // remove value at index 0
            numbers.remove(new Integer(10)); // remove 10
     
            for(int x : numbers)
                System.out.println(x);
            }
        }
    }

    **Note: From jdk1.6, we can declare generics like this: ArrayList<Integer> numbers = new ArrayList<>();

  2. HashSet / Set
    HashSet implements the Set interface and uses hash code to store data. Since, hash code of same values would be same, hence there would not be any duplicate values in the collection. Also, we cannot access elements of HashSet using index. Here is an example:

    import java.util.HashSet;
    
    public class Test {
        public static void main(String[] args) {
            HashSet<Integer> numbers = new HashSet<Integer>();
     
            numbers.add(30);
            numbers.add(60);
            numbers.add(40);
            numbers.add(10);
            numbers.add(20); 
            numbers.add(40);
     
            numbers.remove(new Integer(10));    // remove 10
     
            for(int x : numbers)
                System.out.println(x);
            }
        }
    }

    Other similar classes implementing Set are LinkedHashSet(maintains order of input values) and TreeSet(gives sorted output)

  3. HashMap / Map
    HashMap stores data in the form of key/value pair with no duplicate keys. It is very easy to find data in HashMap if you know the key. Here is an example:

    import java.util.HashMap;
    public class Test {
    
        public static void main(String[] args) {
            HashMap<Integer, String> map = new HashMap<>();
     
            map.put(1, "USA");
            map.put(44, "United Kingdom");
            map.put(91, "India");
     
            int countryCode = 91;
     
            if(map.containsKey(countryCode))
                System.out.printf("Country with code %d is %s", countryCode, map.get(countryCode));
            else
                System.out.printf("Country code %d does not exist", countryCode);
        }
    }
Wildcards

Wildcards are used in Java to cast collections of different types. Let us first check an example:

import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) {

       List<Rectangle> items = new ArrayList<Rectangle>();
 
       items.add(new Rectangle(2,3));
       items.add(new Rectangle(3,4));
       items.add(new Rectangle(4,5));
 
       show(items);
   }
 
   static void show(List<Rectangle> items){
 
     for(Rectangle r : items)
        System.out.println(r.area());
   }
}

class Rectangle{
 
   private int length;
   private int width;
 
   public Rectangle(){}
 
   public Rectangle(int length, int width){
      this.length = length;
      this.width = width;
   }
 
   public int area(){
      return length * width;
   }
 
   public String toString(){
      return "Area = " + area();
   }
}

The code here works fine.

What will you do if we have another class named Square? We have to create another show method in that case.

That is where wildcard helps us. Check the next example:

Using wildcard ?

import java.util.ArrayList;
import java.util.List;

public class Test {

   public static void main(String[] args) {

      List<Rectangle> rectangles = new ArrayList<Rectangle>();
      List<Square> squares = new ArrayList<Square>();
 
      rectangles.add(new Rectangle(2,3));
      rectangles.add(new Rectangle(3,4));
      rectangles.add(new Rectangle(4,5));
 
      squares.add(new Square(5));
      squares.add(new Square(6));
      squares.add(new Square(7));

      show(rectangles);
      show(squares);
   }
 
   static void show(List<?> items){
 
      for(Object r : items)
         System.out.println(r);    // calls r.toString()
   }
}

class Rectangle{
 
   private int length;
   private int width;
 
   public Rectangle(){}
 
   public Rectangle(int length, int width){
      this.length = length;
      this.width = width;
   }
 
   public int area(){
      return length * width;
   }
 
   public String toString(){
      return "Area of rectangle = " + area();
   }
}

class Square{
 
   private int side;
 
   public Square(){}
 
   public Square(int side){
     this.side = side;
   }
 
   public int area(){
     return side * side;
   }
 
   public String toString(){
     return "Area of square = " + area();
   }
}

This time, same show() method can accept any type of list.

Using wildcard ? extends 

In the above example, the show() method accepts all type of lists. If we want to restrict it to some specific classes, we can use ? extends.

The syntax would be:

show( List<? extends A> items )

Now, this method can receive lists of type A or its sub types.

Here is an example:

import java.util.ArrayList;
import java.util.List;

public class Test {

   public static void main(String[] args) {

      List<Rectangle> rectangles = new ArrayList<Rectangle>();
      List<Square> squares = new ArrayList<Square>();
 
      rectangles.add(new Rectangle(2,3));
      rectangles.add(new Rectangle(3,4));
      rectangles.add(new Rectangle(4,5));
 
      squares.add(new Square(5));
      squares.add(new Square(6));
      squares.add(new Square(7));

      show(rectangles);
      show(squares);
   }
 
   static void show(List<? extends Shape> items){
 
      for(Shape r : items)
         System.out.println(r.area());    
   }
}

class Shape{
    public int area(){ return 0; }
}

class Rectangle extends Shape{
 
   private int length;
   private int width;
 
   public Rectangle(){}
 
   public Rectangle(int length, int width){
      this.length = length;
      this.width = width;
   }
 
   public int area(){
      return length * width;
   }
 
   public String toString(){
      return "Area of rectangle = " + area();
   }
}

class Square extends Shape{
 
   private int side;
 
   public Square(){}
 
   public Square(int side){
     this.side = side;
   }
 
   public int area(){
     return side * side;
   }
 
   public String toString(){
     return "Area of square = " + area();
   }
}

Using wildcard ? super

This wildcard restricts the usage to current type or its super type. So, we can say, it is a sort of converse of ? extends.

We can change the above show() method like this:

// receive list of type List<Square> or List<Shape>
static void show(List<? super Square> items){      
 
      for(Object r : items)
         System.out.println(r);    
   }
}