Java Generics

In Java, the term generics means parameterized types. Parameterized types are important because they enable you to create classes, interfaces, and methods in which the data types upon which they operate is specified as a parameter.

Using generics, it is possible to create a single class, for instance, that automatically works with the different types of data. A class, interface, or method which operates on a parameterized type is called generic, as in generic class or generic method.

Java Generics Example

Following is a simple example of a generic class. This program defines the two classes. The first is the generic class Genrc, and the second is GenrcDemo, which uses Genrc.

/* Java Program Example - Java Generics
 * This is a simple generic class
 * Here, T is a type parameter which will be
 * replaced by a real type when an object of
 * the type Genrc is created
 */

class Genrc<T>
{
    T obj;        // declare an object of type T
    
    /* pass the constructor a reference to an 
     * object of the type T     
     */
    Genrc(T o)
    {
        obj = o;
    }
    
    /* return obj */
    T getobj()
    {
        return obj;
    }
    
    /* show type of T */
    void showType()
    {
        System.out.println("Type of T is " + obj.getClass().getName());
    }
}

/* illustrates the generic class */
class GenrcDemo
{
    public static void main(String args[])
    {
        
        /* create a Genrc reference for Integers */
        Genrc<Integer> iObj;
        
        /* create a Genrc<Integer> object and assign its 
         * reference to iObj.
         * Notice here that the use of autoboxing to encapsulate the
         * value 88 within an Integer object  
         */
        iObj = new Genrc<Integer>(88);
        
        /* show the type of data used by iObj */
        iObj.showType();
        
        /* get the value in iObj.
         * Notice that no cast is needed 
         */
        int v = iObj.getobj();
        System.out.println("value : " + v);
        
        System.out.println();
        
        /* create a Genrc object for Strings */
        Genrc<String> strObj = new Genrc<String> ("Generics Test");
        
        /* show the type of data used by strObj */
        strObj.showType();
        
        /* get the value of strObj.
         * Again, notice that no cast is needed
         */
        String str = strObj.getobj();
        System.out.println("value : " + str);
        
    }
}

The output produced by the above Java program is shown here:

java generics

Now let's examine this program carefully in detail.

First, notice how Genrc is declared by the following line :

class Genrc<T>
{

Here, T is the name of type parameter which is used as a placeholder for the actual type that will be passed to the Genrc whenever an object is created. Thus, T is used within Genrc when the type parameter is needed. Notice here that T is contained inside < >. This syntax can be generalized. When a type parameter is being declared, it is specified within angle brackets. Because the Genrc uses type parameter, Genrc is a generic class, which is also called a parameterized type.

Next, T is used to declare an object called obj, as shown below :

T obj;      // declare an object of type T

As explained, T is a placeholder for the actual type, will be specified whenever Genrc object is created. Therefore, obj will be an object of the type passed to T. For instance, if type String is passed to T, then in that instance, obj will be of the type String.

Now consider Genrc's constructor:

Genrc(T o)
{
   obj = o;
}

Notice that its parameter, o, is of the type T. This means that the actual type of o is determined by the type passed to T when Genrc object is created. Also, as both parameter, o and member variable obj are of the type T, they both will be of the like actual type when Genrc object is created.

The type parameter T can also be used to specify the return type of a method, as in the case with the getobj() method, shown below :

T getobj()
{
   return obj;
}

Because obj is also of the type T, its type is compatible with the return type that specified by the method named getobj().

The showType() method displays the type of T by calling the getName() on the Class object returned by the call to the getClass() on obj. The getClass() method is defined by Object and is thus a member of class types. It returns a Class object that corresponds to the type of the class of the object on which it is called. Class defines the getName() method, which returns a string representation of the class name.

The class GenrcDemo illustrates the generic Genrc class. It first creates a version of Genrc for integers, as shown below :

Genrc<Integer> iObj;

Now let's look closely at this declaration. First, notice that the type Integer is specified within the angle brackets after Genrc. In this case, Integer is a type argument that is passed to Genrc's type parameter, T. This effectively creates Genrc variant in which all the references to the T are translated into references to Integer. Thus, for this declaration, obj is of type Integer, and the return type of the getobj() is of type Integer.

Before moving on, it's necessary to state that the Java compiler doesn't actually create different versions of Genrc, or of any other generic class. Although it is helpful to think in these terms, it is not what actually happens. Instead, the compiler removes all the generic type information, replacing the necessary casts, to make your code behave as if a specific version of Genrc were created. Thus, there is truly only one Genrc version that actually exists in your program. The process of removing the generic type information is called erasure

The next line assigns to iObj, a reference to an instance of Integer version of Genrc class:

iObj = new Genrc<Integer>(88);

Notice here that when the Genrc constructor is called, the type argument Integer is specified also. It is because the type of object (iObj in this case) to which the reference is being assigned is of Genrc<Integer> type. Thus, the reference returned by the new operator/keyword must also be of Genrc<Integer> type. If it is not, a compile-time error will result. For example, the following assignment will cause a compile-time error :

iObj = new Genrc<Double>(88.0);    // Error!

Because iObj is of type Genrc<Integer>, it can not be used to refer to an object of Genrc<Double>. This type checking is one of the main benefits of generics as it ensures type safety.

As the comments in the program state, the assignment :

iObj = new Genrc<Integer>(88);

makes the use of autoboxing to encapsulate the value 88, which is an int, into an Integer. This works because Genrc<Integer> creates a constructor that takes an Integer argument. Because an Integer is expected, Java will automatically box 88 inside one. Of course, the assignment could also have been written explicitly, like:

iObj = new Genrc<Integer>(new Integer(88));

However, there would be no benefit to using this version.

The program then display the type of obj within iObj, which is Integer.

Next, the program obtains the value of obj by the use of the following line:

int v = iObj.getobj();

Because the return type of the getobj() is T, which was replaced by Integer when iObj was declared, the return type of the getobj() is also Integer, which unboxes into int when assigned to v (that is an int). Therefore, there is no need to cast the return type of the getobj() to Integer.

Of course, it is not necessary to use the auto-unboxing feature. The preceding line could have been written like this, too:

int v = iObj.getobj().intValue(;

However, the auto-unboxing feature makes the code more compact.

Next, GenrcDemo declares an object of type Genrc<String> :

Genrc<String> strObj = new Genrc<String>("Generics Test");

Because the type argument is String, String is replaced for T inside Genrc. This creates conceptually a String version of Genrc, as the remaining lines in the program demonstrate.

Java Online Test


« Previous Tutorial Next Tutorial »