Java Generics Tutorial With Examples

By Vijay

By Vijay

I'm Vijay, and I've been working on this blog for the past 20+ years! I’ve been in the IT industry for more than 20 years now. I completed my graduation in B.E. Computer Science from a reputed Pune university and then started my career in…

Learn about our editorial policies.
Updated March 7, 2024

Java Generics are a Set of Features That Allow You to Write Code Independent of the Data Type. This article explains Java Generics in Detail With Examples:

Generics are one of the important features of Java and were introduced from Java 5 onwards.

By definition, Generics are a set of Java language features that allow the programmer to use Generic types and functions and thus ensure type safety.

Java Generics

How Do Generics Work In Java?

If you have worked with C++ before, then Java Generics is the same as templates in C++. Java Generics allow you to include a parameter in your class/method definition which will have the value of a primitive data type.

For Example, you can have a Generic class “Array” as follows:

Class Array<T> {….}

Where <T> is the parameterized type.

Next, you can create objects for this class as follows:

Array int_array<Integer> = new Array<Integer> ()
Array<Character> char_array = new Array<Character> ();

So given a Generic parameterized class, you can create objects of the same class with different data types as parameters. This is the main essence of using Java Generics.

Similarly, you can write a generic method with a parameterized type for sorting an array and then instantiate this method to any primitive type.

Java Generics are mostly used with the collections framework of Java. The different collections like LinkedList, List, Map, HashMap, etc. use Generics for implementation. Generics provide type-safety as the type checking is done at compile time thus making your code more stable.

Let us now more into the details of Generic classes and methods as well as other related topics.

Generic Classes

A Generic class is the same as a normal class except that the classname is followed by a type in angular brackets.

A general definition of a Generic class is as follows:

class class_name<T>
{
class variables;
…..
class methods;
}

Once the class is defined, you can create objects of any data type that you want as follows:

class_name <T> obj = new class_name <T> ();

For Example, for Integer object the declaration will be:

class_name <Integer> obj = new class_name<Integer>;

Similarly, for the String data type, the object will be:

class_name <String> str_Obj = new class_name<String>;

An example implementation for the Generic class is shown below.

class MyGenericClass<T>
{  
    T obj;  
    void add(T obj)
    {
           this.obj=obj;
    }  
    T get()
    {
           return obj;
    }  
}  

class Main
{  
     public static void main(String args[])
     {  
           MyGenericClass<Integer> m_int=new MyGenericClass<Integer>();  
           m_int.add(2);
           MyGenericClass<String>mstr=new MyGenericClass<String>();  
           mstr.add("SoftwaretestingHelp");

           System.out.println("Member of MyGenericClass<Integer>:" + m_int.get());
           System.out.println("Member of MyGenericClass<String>:" + mstr.get());
     }
}  

Output:

Implementation for generic class output

In the above program, a class MyGenericClass is a generic class. It has two methods i.e. add and get. The method add initializes the generic object while the get methods return the object.

In the main function, we declare two objects of Integer and String type each. We initialize both these objects with their respective initial values using the add method and then output the contents of these objects using the get method.

We presented the Generic class example above with one type parameter. But in reality, a class can have more than one type parameter as well. In this case, the type parameters are separated by a comma.

The following example demonstrates this:

classTest_Generics<T1, T2>
{
    T1 obj1;  // An object of type T1
    T2 obj2;  // An object of type T2

    // constructor to initialise T1 & T2 objects
    Test_Generics(T1 obj1, T2 obj2)
    {
        this.obj1 = obj1;
        this.obj2 = obj2;
    }

    public void print()
    {
        System.out.println("T1 Object:" + obj1);
        System.out.println("T2 Object:" + obj2);
    }
}

class Main
{
     public static void main (String[] args)
    {
         Test_Generics<String, Integer>obj =
             newTest_Generics<String, Integer>("Java Generics", 1);

         obj.print();
    }
}

Output:

Type parameters output

In this program, we have two type parameters i.e. T1 and T2. We have functions to initialize the member objects and also to print the contents. In the main function, we declare an object with two types i.e. String and Integer. The output of the program shows the contents of the created object.

Just like classes, you can also have Generic interfaces. We will learn all about interfaces in a separate topic.

Java Generic Methods

Just as you can have Generic classes and interfaces, you can also have Generic methods in case you do not need an entire class to be Generic.

The following program shows the implementation of the Generic method “printGenericArray”. Note the method call in the main function. Here we make two calls to the Generic method, first time with <Integer> type and then with <String> type.

public class Main{   
public static < T > void printGenericArray(T[] items) {  
         for ( T item : items){          
               System.out.print(item + " ");  
         }  
         System.out.println();  
    }  
    public static void main( String args[] )
    {  
        Integer[] int_Array = { 1, 3, 5, 7, 9, 11 };  
        Character[] char_Array = { 'J', 'A', 'V', 'A', 'T','U','T','O','R','I','A', 'L','S' };  

        System.out.println( "Integer Array contents:" );  
        printGenericArray(int_Array  );   

        System.out.println( "Character Array contents:" );  
        printGenericArray(char_Array );   
    }   
}

Output:

Generic Methods output

Bounded Type Parameters

Bounded Type Parameters come into picture when you want to limit the data types in Generics. For Example, if you want that a particular generic class or method or any interface that should work only for numeric data types, then you can specify that using the “extends” keyword.

This is shown below:

List<? extends Number> myList = new ArrayList<Long>();
          List<? extends Number> list1 = new ArrayList<Integer>();

The above two declarations will be accepted by the compiler as Long and Integer are subclasses of Number.

The next declaration, however, is going to be a problem.

List<? extendsNumber> list = new ArrayList<String>();

This will give a compile-time error because String is not a Number. The symbol ‘?’ in the above example is known as a wildcard and we will discuss it next.

So in general, bounded type parameters are mostly used when you want to restrict the data types to be used in your generics code.

Java Generics Wildcard

In Java, a Wildcard is denoted by a question mark, ‘?’ that is used to refer to an unknown type. Wildcards are mostly used with generics as a parameter type.

When using Generic Wildcards, you must remember one point that although the object is the superclass of all other classes, the collection of objects (For Example, List<objects>) is not a superclass of all other collections.

Apart from being used as a parameter type, you can use a wildcard as a field, a local variable and as such. However, you can never use a wildcard as a supertype, or as a type argument to invoke generic method or in case of creation of an instance of a generic class.

There are many examples of wildcard parameterized types (here at least one type argument is a wildcard) as shown below and the wildcards used at different places will be interpreted differently:

  • Collection<? <: Collection denotes all collection interface instantiation irrespective of the type argument used.
  • List<? extends Number<: List represents all list types where element type will be a number.
  • Comparator<? super String>: All comparator interface instantiations for type arguments that are Stringsupertypes.

Note that a Wildcard Parameterized type is a rule that is imposed to recognize valid types. It is not a concrete data type. Generic Wildcards can be bounded or unbounded.

#1) Unbounded Wildcards

In Unbounded Wildcards, there are no restrictions on type variables and is denoted as follows:

ArrayList<?> mylist = new ArrayList<Integer>();
ArrayList<?> my_strList = new ArrayList<String>();

#2) Bounded Wildcards

We have already discussed bounded types. These put the restrictions on the data type used to instantiate the type parameters using the keywords – extends or super. These wildcards can be further divided into Upper Bounded Wildcards and Lower Bounded Wildcards.

  • Upper Bounded Wildcards

If you want that your generic expression to be valid for all the subclasses of a given type then you specify the Upper Bounded Wildcard with the keyword extends.

For Example, suppose you require a generic method that supports List<Integer>, List<Double>, etc. then you can specify an Upper Bounded Wildcard like List<? extends Number>. As Number is a superclass, this generic method will work for all its subclasses.

The following program demonstrates this.

importjava.util.*;

public class Main<T>
{
    private static Number summation (List<? extends Number> numbers){
      double sum = 0.0;
      for (Number n : numbers)
          sum += n.doubleValue();
      return sum;
   }

   public static void main(String[] args)
   {
      //Number subtype : Integer
      List<Integer>int_list = Arrays.asList(1,3,5,7,9);
      System.out.println("Sum of the elements in int_list:" + summation(int_list));

      //Number subtype : Double
      List<Double> doubles_list = Arrays.asList(1.0,1.5,2.0,2.5,3.0,3.5);
      System.out.println("Sum of the elements in doubles_list:" + summation(doubles_list));

   }
}

Output:

Upper Bounded wildcards output

Here we have provided an upper bound wildcard, List<? extends Number> to the type argument of function “summation”. In the main function, we define two lists i.e. int_list of type Integer and doubles_list of type Double. As Integer and Double are the subclasses of Number, the function summation works perfectly on both these lists.

  • Lower Bounded Wildcards

If you want the generic expression to accept all the superclasses of a particular type then you can specify a Lower Bounded Wildcard for your type argument.

An example implementation for this is given below:

importjava.util.*;

class Main
{
public static void main(String[] args)
    {
        //Integer List
        List<Integer>Int_list= Arrays.asList(1,3,5,7);

        System.out.print("Integer List:");
        printforLowerBoundedWildcards(Int_list);

        //Number list
        List<Number>Number_list= Arrays.asList(2,4,6,8);

        System.out.print("Number List:");
        printforLowerBoundedWildcards(Number_list);
    }

     public static void printforLowerBoundedWildcards(List<? super Integer> list)
    {
           System.out.println(list);
    }
}

Output:

Lower Bounded wildcards ouput

In this program, the Lower Bounded Wildcard specified is “List<? super Integer>”. Then in the main function, we have a <Integer> type list and the <Number> list. As we have used the Lower Bounded Wildcard, the Number class is a superclass of Integer is a valid type argument.

Advantages Of Java Generics

#1) Type Safety

Generics ensure Type Safety. This means that type checking is done at compile time rather than at the run time. Thus there is no chance of getting “ClassCastException” during runtime as correct types will be used.

importjava.util.*;

class Main
{
     public static void main(String[] args)
    {
        List mylist = new ArrayList();    
        mylist.add(10);  
        mylist.add("10");  
        System.out.println(mylist);
        List<Integer> list = new ArrayList<Integer>();    
        list.add(10);  
        list.add("10");// compile-time error  
        System.out.println(list);
    }
}

In the above program, we have two lists, one without generics and another with generics. In the non-generic list, there is no Type Safety. You can add an integer, string, etc. as an element and it is accepted.

In the generic list, you can add only one type of element that is specified in the generic expression. If you attempt to add an element of another type, then it results in a compile-time error.

In the above program the compile-time error is flashed at the line:

list.add("10"); 

#2) Code Reusability

Using Generics, you need not write separate code for each data type. You can write a single class or method etc. and use it for all data types.

#3) No Need For Typecasting

As you are using Generics, the compiler knows about the types used, then there is no need for typecasting.

Consider the below code:

List mylist = new ArrayList();    
      mylist.add("Java");    
      String mystr = (String) list.get(0); //typecasting   required

As you can see when a normal list is used you need to typecast the list element to its appropriate type the way it is done for the mystr above.

Now let us write the same code again with a generic list.

List<String> list = new ArrayList<String>();    
          list.add("Java");    
          String mystr = list.get(0);    

Here, we have specified the string type as a generic expression for the list declaration. Thus, to retrieve individual elements of this list, we need not typecast.

#4) Implement Generic Algorithms

You can implement a lot more Generic algorithms when you use Generics to code.

#5) Compile-Time Checking

As already mentioned, when you use Generics in your Java program, the compiler checks the types at the compile time thus preventing abnormal termination of the program at runtime.

Frequently Asked Questions

Q #1) Why do we use Generics in Java?

Answer: Generics ensure type independence i.e. we can provide a type parameter while defining a class/interface/method etc. so that during the actual instantiation we can specify the actual type. This way we also provide code reusability.

Q #2) Are Generics important in Java?

Answer: Yes. In fact, Generics are the most important features of Java to ensure type safety i.e. compile-time type checking.

Q #3) When did Java add Generics?

Answer: Generics were added to Java in 2004 with J2SE 5.0 with an intention to ensure compile-time type safety in Java.

Q #4) What is a Generic type?

Answer: A Generic type is a Generic Class, Interface or Method that is provided with a type parameter. This allows for type safety and code reuse.

Q #5) Can we use Generics with Array in Java?

Answer: No. Java does not allow generic arrays.

Conclusion

This concludes the tutorial on Java generics which is considered as one of the most important features in recent Java versions. Using Generics in Java programs ensures type safety as well as code reuse. It also ensures compile-time checking so that the program does not break at runtime.

Java generics come in handy mostly with Java collections interface which we will discuss in detail in another tutorial in this series.

Happy Reading!!

Was this helpful?

Thanks for your feedback!

Leave a Comment