Java Generics Complete Tutorial with Examples

Java Generics is one of the most common and important feature of Java. Java Generics was introduced in Java version 5.

Developers who are working on Java version 5 or higher very commonly uses Generics in Java. Java Generics is often used with Java Collections framework.

Java Generics is very simple and easy to use but it provides many useful and important features.

In this article, We have explained Java Generics in very easy language and with illustrative examples and we have also covered interview questions and answers related to Java Generics.

So Let’s get started :

Introduction to Java Generics

Java Generics was basically introduced to make the objects type safe or we can say to deal with the type safe objects.

Java Generics provides compile-time type checking and we all know that compile time errors are far better than getting Runtime errors.

Generics in Java removes the possibility of getting ClassCastException. We all must have faced this error while working with Collections in Java. When Generics was introduced in Java version 5, then the whole collection framework was re-written so that Collection can be made type safe using Java Generics.

Before Java Generics , we were able to store any type of objects in the Collection as it was non generic. Now, if you are using Generics in Java, programmers are forced to store only specific type of object in Collection.

Let’s understand the problem without Java Generics using Example :

ArrayList al = new ArrayList();
al.add("Test");         // Added String into ArrayList al
al.add(new Integer(2)); // Added Integer into same ArrayList al
// We have stored non-generic data into ArrayList 
for(Object obj : al){
    
    //Now, the below line will throw ClassCastException at Runtime 
    //because Integer cannot be type cast to String
    String str=(String) obj; 
}

The above code will surely not throw any errors during compile-time but it will throw the ClassCastException at RunTime as we are typecasting all the elements of ArrayList to String but there is one integer present in the list.

Let’s see , How this problem can be solved by using Java Generics :

ArrayList<String> al = new ArrayList<String>(); // Using Generics for type-safety
al.add("Test");         
//al.add(new Integer(2)); //Will throw compile time error if non-commented
for(String str : al){
    
    //Now, Casting is not required
    //And No risk of ClassCastException
}

We have specified at the time of ArrayList creation that it will only store String. So if we try to add other type of object than String then it will throw Compile time error.

Java Generics Example

import java.util.*;
class FullGenericEx{
public static void main(String args[]){
   ArrayList<Integer> list=new ArrayList<Integer>();
    list.add(5);
    list.add(8);
    //list.add("TechBlogStation"); //compile time error
   Integer n=list.get(1);   //type casting is not required
   System.out.println("Integer : "+n);
   Iterator<Integer> iterate=list.iterator();
   
    while(iterate.hasNext()){
      System.out.println(iterate.next());
    }
   }
}

Output :

Integer : 8
5
8

Java Generics Example using Map

import java.util.*;
class GenericUsingMap{
 public static void main(String args[]){
   Map<Integer,String> hm=new HashMap<Integer,String>();
    hm.put(1,"Tech");
    hm.put(2,"Blog");
    hm.put(3,"Station");
  Set<Map.Entry<Integer,String>> set=hm.entrySet();
  Iterator<Map.Entry<Integer,String>> iterate=set.iterator();
    while(iterate.hasNext()){
      Map.Entry e=iterate.next();     //Type casting not required
      System.out.println(e.getKey()+" "+e.getValue());
     }
   }
}

Output :

1 Tech
2 Blog
3 Station 

Advantage of Java Generics

Java Generics has main three advantages. They are :

  • Type Safety of Objects
  • No Type Casting required
  • Compile-time checking

Type Safety of Objects

Java Generics provides type safety to Objects. In Java Generics, we can only store single type of objects. It does not allow to store any other type of Object in the Collection.

So It provides type safety. Let’s see below example :

// Without Generics
List list1 = new ArrayList();    
list1.add(new Integer(123));  
list1.add("Test");  
//With Generics
List<Integer> list2 = new ArrayList<Integer>();  // list2 is type safe as it can only store Integer
list2.add(new Integer(123));  
list2.add("Test"); // compile-time error

No Type Casting required

If we are not using Java Generics, we are required to type cast the Objects as the Collection can store any type of Object.

With Java Generics, Type casting is absolutely not required . See the Example :

// Without Generics
List list1 = new ArrayList();    
list1.add("Testing");    
String str = (String) list1.get(0);  //Type Casting
//With Generics
List<String> list2 = new ArrayList<String>();    
list2.add("Testing");    
String str = list2.get(0); // Type Casting not required as only String can be stored in the list  

Compile-time checking

Java Generics checks for errors at Compile time so that Runtime errors can be prevented. We all as developers know the fact that compile time errors are way better than the Runtime errors.

List<Integer> list1 = new ArrayList<Integer>(); 
list2.add(new Integer(54));  
list2.add("Testing"); // compile-time error

Java Generic Class

Java Generics is not only limited to Collection Objects. We can create our own classes using Generic Types.

A class in Java is called to be generic if the class declares one or more type variables. Those type variables are also known as the type parameters of that class. 

Generic type classes or a interfaces are basically parameterized over types. In Java, angular brackets (<>) are used to specify the type parameters.

Let’s understand the problem without using Java Generic class:

package com.techblogstation.examples;
public class SimpleClass {
	private Object object;
	public Object get() {
		return object;
	}
	public void set(Object object) {
		this.object = object;
	}
        public static void main(String args[]){
        
		SimpleClass type = new SimpleClass();
		type.set("TechBlogStation"); 
		String name = (String)type.get(); //Type casting is required and there is
                   //possibility of ClassCastException
	}
}

We can see in the above example that we are required to do type casting and thus it can be produce Runtime exception – ClassCastException.

Let’s see How this problem can be solved using Java Generics in the below section :

Generic Class Example Java

In the below example, we are using the T type parameter to create one generic class of specific type. The T type in Java Generics indicates that it can refer to any type e.g. Integer, String or any other class.

package com.techblogstation.examples;
public class GenericTypeClass<T> {
	private T object;
	public Object get() {
		return this.object;
	}
	public void set(Object object) {
		this.object = object;
	}
        public static void main(String args[]){
        
		GenericTypeClass<String> type1 = new GenericTypeClass<>(); // Defining the Type
		type1.set("TechBlogStation");  // It is valid
		
		GenericTypeClass type2 = new GenericTypeClass(); // Not defining the Type
		
		type2.set("TechBlogStation");  // It is valid
		type2.set(2);  // It is also valid and Yes AutoBoxing is supported
	}
}

We can see in the main method of our class that how we have used the GenericTypeClass. We are not required to do the type casting and there is no possibility of ClassCastException at Runtime.

One thing is important in Java Generics to remember that if we do not provide the specific type at creation then compiler will show us warning that “GenericTypeClass” is raw type.

We should also remember that the references for generic type GenericTypeClass should be parameterized. In case, we do not provide the type then it will allow all type of Objects then it will lead to need of type casting and then there will be possibility that Runtime exception ClassCastException can occur.

Java Generic Interface

Similar to Java Generics class , we can create Generic Interfaces in java as well.

We can have single or multiple type parameters in the interface as well.

E.g. as we have in Map interface.

We can also have parameterized value to the parameterized type as well.

E.g. new HashMap<Integer, List<String>>(); is completely valid.

There are multiple Generic interfaces exist in Java. One of the most common Generic interface is Comparable. Let’s look how this interface is written :

package java.lang;
import java.util.*;
public interface Comparable<T> {
    public int compareTo(T o);
}

Java Generic Type

Java has some naming convention rules to make the code more readable and understandable. Naming convention for any programming language is very essential as it helps the programmers in many ways.

We have naming convention in Java Generic Type as well to make the code easy to understand, Java Generics has some own naming convention. Let’s understand :

  • Type parameters are single letter.
  • Type parameters are Uppercase letter.
  • The most common type parameter names are :

T — Type
V — Value
E — Element
K — Key
N — Number

Java Generic Method

As we have seen in the previous section, that we can create the whole class as Generic but if we do not want the complete class to made generic and parameterized then we can create Generics method .

Not just methods, we can make Java Constructors generic too as constructors are also special type of methods in Java.

Generic Method Example Java

package com.techblogstation.examples;
public class GenericMethodEx {
	// Below is the Java Generics Method
	
	public static <T> boolean isEqual(GenericTypeClass<T> p1, GenericTypeClass<T> p2){
		return p1.get().equals(p2.get());
	}
	
	public static void main(String args[]){
	
	//Creating Object of class which we created in previous section
		GenericTypeClass<String> p1 = new GenericTypeClass<>();
		p1.set("TBS");
		
		GenericTypeClass<String> p2 = new GenericTypeClass<>();
		p2.set("TBS");
		
		isEqual = GenericMethodEx.isEqual(p1, p2);
	
	}
}

We can see that the generic method in above example shows the syntax to using the generic types in Java methods. We can specify the actual type when we are calling these methods. Java Compiler itself determines what type of variable to use and this phenomena is called as type inference.

Java Generics Wildcards

Wildcards in Java Generics are also very much essential. In Generics, the symbol question mark (?) is the WildCard and it represents the unknown type.

The symbol ? represents the wildcard element in Java.

E.g. <? extends Number> means that any subclass of Number can be ? element.

The Wildcards are used as type of a parameter, local variable, fields and return type etc. However wildcards cannot be used when we are invoking a generic method or we are instantiating a generic class.

Let’s understand different type of WildCards in Generics :

Java Generics Upper Bounded Wildcard

Upper Bounded Wildcards are basically used to decrease the restrictions on a variable. It is used to restrict the unknown type to be one specific type or also a subtype of that type.

Upper Bounded Wildcards are declared with wildcard character ? followed by the extends or implements in case of class or interface respectively and then followed by its upper bound.

Syntax :

List<? extends Number>  


?  – wildcard character.
extends – keyword.
Number – class present in java.lang package

Now let’s understand how Upper Bounded Wildcards are less restrictive. Suppose we want our method to accept only list of Number and its subtypes like Double, Integer etc.

Upper Bounded wildcard is like List<? extends Number> and it is less restrictive than the List<Number> as List<Number> will accept the List of Numbers only but List<? extends Number> will accept type Number and its subclasses as well.

So Upper Bounded Wildcards are less restrictive.

Example of Upper Bounded Wildcard

import java.util.ArrayList;
public class UpperBoundedExample{
	//Below method have a parameter which is Upper Bounded WildCard
	private static Double sum(ArrayList<? extends Number> listNum) {
	
		double result=0.0;
		
		for(Number num:listNum)
		{
			result = result + num.doubleValue();
		}
		
		return result;
	}
	public static void main(String[] args) {
		//Creating a List of Integer
		ArrayList<Integer> list=new ArrayList<Integer>();
		list.add(10);
		list.add(20);
		System.out.println("Result of Addition = "+sum(list));
		
              //Creating a list of Double
		ArrayList<Double> list2=new ArrayList<Double>();
		list2.add(30.0);
		list2.add(40.0);
		System.out.println("Result of Addition of Double list= "+sum(list2));
		
		
	}
	
}

In the above example, we can see that our method implementation is working for Integer and Double both as these two are subclasses of Number.

Java Generics Unbounded Wildcard

The Unbounded Wildcard helps is situation where we want our generic method to accept an unknown type means all types such as List<?>.

Now this list is accepting an unknown type so we can pass any type to the list and it will accept that type.

Unbounded wildcards can be used in below scenarios :

  • If the method is implemented using the functionality provided in the Object class as Object is the superclass of every class in Java.
  • If the Generic class is containing the methods which do not depend upon the type parameter.

Example of Unbounded Wildcard

import java.util.*;
public class UnboundedExample {
//Below is the method having parameter with Unbounded Wildcard
	public static void show(List<?> list)
	{
		
		for(Object obj:list)
		{
			System.out.println(obj);
		}
		
	}
	
	
	public static void main(String[] args) {
	
//List of Strings
        List<String> list1=Arrays.asList("Five","Four","Two");
	  System.out.println("Show the List of String");
		show(list1);
//List of Integers
	List<Integer> list2=Arrays.asList(5,4,2);
	  System.out.println("Show the List of Integer");
	        show(list2);
	
	}
}

In the above example, we can see that our method implementation is working for Integer and String both as we have used Unbounded Wildcards.

Java Generics Lower bounded Wildcard

Lower Bounded Wildcards are used to restrict the unknown type to be one specific type or a supertype of that type.

Lower Bounded Wildcards are declared with wildcard character ? followed by the super keyword and then followed by its lower bound.

Syntax :

List<? super Integer>  


?  – wildcard character.
super – keyword.
Integer – Wrapper class

Lower Bounded Wildcards are used in the scenario , Where we want our method to accept the object of provided class or its super class.

For Example :

If we want our method to accept the List of Integer and also its super classes which are Number and Object then we will use Lower Bounded Wildcards :

List<? super Integer> , it will accept the list of Integer as well as List of Integer’s super class. It is less restrictive.

Example of Lower Bounded Wildcard

import java.util.*;
public class LowerBoundExample {
//Below is the method accepting Lower Bounded Wildcards
	public static void show(List<? super Integer> list) {
		for(Object num:list)
		{
			  System.out.println(num);
		}
		
	
	    
	}
public static void main(String[] args) {
	
//List of Integers
	List<Integer> list1=Arrays.asList(10,9,8,7);
	  System.out.println("Showing the list of Integers");
	show(list1);
//List of Numbers	
	List<Number> list2=Arrays.asList(10.5,9,8.3,7.2);
	  System.out.println("Showing the list of Numbers");
	show(list2);
}
}

In the above example, we can see that our method implementation is working for Integer and Number both as Number is the superclass of Integer.

Java Generics Type Erasure

As we have learnt , Java Generics was added with the main purpose of type safety at compile time. Generics has no role during Runtime.

Java compiler uses one feature called as – Type Erasure which removes all the generics type checking code from the byte code and also inserts the type casting if required.

The responsibility of Type Erasure feature is to ensure that new classes are not created for the parameterized type so that Generics does not create any runtime overhead.

Suppose , if we have a Generic class as below in our code :

public class Example<T extends Comparable<T>> {
    private T data;
    private Example<T> next;
    public Example(T data, Example<T> next) {
        this.data = data;
        this.next = next;
    }
    public T getData() { return this.data; }
}

The Java compiler replaces the bounded type parameter T in the above example with the first bound interface, Comparable. This is called Type Erasure. See below :

public class Example {
    private Comparable data;
    private Example next;
    public Node(Comparable data, Example next) {
        this.data = data;
        this.next = next;
    }
    public Comparable getData() { return data; }
}

What is not allowed to do with Java Generics?

There are certain restrictions with using Generics. Let’s look at them :

A) We cannot create static field of type

We are not allowed to create a static generic parameterized member in Generics. If we will create one, Java compiler will throw us compile-time error stating “Cannot make a static reference to the non-static type T”.

public class GenericTest<T>
{
   private static T data; //Compile-time error
}

B) We cannot create instance of T

Generics does not allow to create the instance of T. If we will do so, Java compile will throw error stating “Cannot instantiate the type T”.

public class GenericTest<T>
{
   public GenericTest(){
      //Not allowed
      new T();
   }
}

C) Generics cannot be declared with primitives

Generics does not allow primitives in the declaration. We cannot declare any generic expression like List or List etc.

We can use the respective wrapper classes in place of the primitives and we can then use the primitives at the time of passing the value then it will be accepted as auto-boxing is supported in Generics.

final List<Double> list = new ArrayList<>();   //Allowed
final List<double> list = new ArrayList<>();    //Not allowed

D) Generic Exception class cannot be created

We cannot create Exception classes using Generics .

public class CustomException<T> extends Exception {}
//Compile time error

If you try to create Generic exception class then Java Compiler will throw error stating “The generic class CustomException may not subclass java.lang.Throwable”.

Interview Q & A for Java Generics

What is Generics and in which Java version it was released ?

Generics in Java is a concept which is used to provide type safety to objects and it was released in Java version 5.

Why do we use Generics in Java ?

Generics is basically used to :

  • provide type safety to Object.
  • remove the Runtime error ClassCastException possibility.
  • reduce the requirement of Type Casting.

What is T in Generics ?

<T> is used to create the generic class, method or interface. The T represents type. It is replaced with the actual type when we invoke the method, or instantiate the class etc.

What is Type Erasure in Java?

Type Erasure is the feature in Java which removes all the generics type checking code from the byte code and also inserts the type casting if required.

Will this line of code compile – List<Number> numbers = new ArrayList<Integer>(); ?

No, As Generic does not support sub-typing because sub-typing will cause issue in achieving type-safety.

As the above line of code will require type casting so it is not allowed in Generics.

Can we create Generic Array in Java – List<Integer>[] array = new ArrayList<Integer>[5] ?

No, Generic Array cannot be created in Java.

Conclusion

In this article, we have learnt Java Generics, Its type, Its advantages and uses will the illustrative examples. We have also learnt Type Erasure feature and Interview questions and answers for Generics.

Newsletter Updates

Enter your name and email address below to subscribe to our newsletter

Leave a Reply