Generics in Java With Examples

The term generics means parameterized types. Generics were released in Java 5 to provide compile-time type checking and removing the risk of ClassCastException that was frequently occurring while dealing with collections. Let us understand this concept using an example. We all have learned the concept of overloading and are aware of the purpose of it i.e Polymorphism. Now, imagine we have a calculation class for every data type ( e.g Integer, Float, Double )  that performs the exact same operation.  As a result, we are having multiple classes with the same modus operandi, with only the difference of data type. What if we have just one class, where we can pass the data type as a parameter while creating an object. In Java, this can be done and is known as Generics.  A class, interface, or method that operates on a parameterized type is called generic, as in generic class or generic method.

In generics, the type/types are Objects and not the primitive data types. Which means we can use Integer, but not int. Using primitive data types will result in a compile-time error.

Generic Class

General Format:

//The declaration of the class
class class-name<type-param-list > { // …The code
}

//The Instance of the generic class
class-name<type-arg-list > var-name = new class-name<type-arg-list >(cons-arg-list);

Example(Class Declaration):

class GenericTestInteger{ 
    Integer obj; 
    Test(Integer obj){  
    this.obj = obj;  
    }  
    public Integer getObject(){ 
    return this.obj; 
    } 
} 

class GenericTestFloat{ 
    Float obj; 
    Test(Float obj){  
    this.obj = obj;  
    }  
    public Float getObject(){ 
    return this.obj; 
    } 
}

//Instead of having 2 classes which are performing exact same operation,
// we can have only one class. A Generic class with type as T
class GenericTest<T> 
{ 
    T obj; //an object of type T 
    Test(T obj){  
    this.obj = obj;  
    }  
    public T getObject(){ 
    return this.obj; 
    } 
} 

Example(Instantiation of the classes):

//Instance of GenericTestInteger
GenericTestInteger myIntObj = new GenericTestInteger(25);

//Instance of GenericTestFloat
GenericTestFloat myFloatObj = new GenericTestFloat(11.2f);


//Instance of GenericTest
//Below is an equivalent object creation using Generic class GenericTest
GenericTest<Integer> myIntObj = new GenericTest<Integer>(25);
GenericTest<Float> myFloatObj = new GenericTest<Float>(11.2f);

Here we have passed just one type parameter. We can have multiple type parameters separated by comma inside <>.

Example(Declaration):

class GenericTest<T,X> 
{ 
    T obj; //an object of type T
    X obj2; //an object of type T 
    Test(T obj, X obj2){  
    this.obj = obj;  
    this.obj2 = obj2;
    }  
    public T getObject(){ 
    return this.obj; 
    } 
    public X getObject2(){ 
    return this.obj2; 
    }
} 

Example(Instantiation):

//Instance of GenericTest
GenericTest<Integer,Float> myIntObj = new GenericTest<Integer,Float>(25,11.2f);

Generic methods

The methods can also be type generic similar to classes. 

Declaration:

//The declaration of the method
access-specifier <type-param-list > return-type method-name(parameter-list){ 
// …The code
}

Example:

public class GenericTest { 
    public static void main(String[] args) 
    { 
        genPrint(25); 
        genPrint("TechiClues"); 
        genPrint(25.87f); 
        genPrint(25.87); 
    } 
    
    // A Generic method  
    static <T> void genPrint(T inp) { 
        System.out.println("I am reference type "+inp.getClass().getName() +" and my value is "+inp); 
    } 
}

Output:

I am reference type java.lang.Integer and my value is 25
I am reference type java.lang.String and my value is TechiClues
I am reference type java.lang.Float and my value is 25.87
I am reference type java.lang.Double and my value is 25.87

Advantages

Reusability

We have seen in our previous examples that we can write a method/class/interface once and use it for any reference type we want.

Compile-time type checking

Generics highlights any ClassCastException at the compile-time instead of an unwanted exception at run time. Relating to our previous example, if we pass an Integer in place of Float to the GenericTest<Float>, it will be accepted due to upcasting. However, if we try to attempt to reverse i.e passing Float to GenericTest<Integer>, it will be a compile error, due to type checking.

This was an overview of Java Generics. We will be discussing Lambda Expressions in the next lecture.