Saturday, March 2, 2013

Generics in Java

Generics implement the Parameterized Type concept in Java and it's one of the major changes of Java 5 (Java SE5). This is derived from a concept known as template of C++ and initial motivation behind this feature was to allow creation of parameterized containers.

      List list = new ArrayList();    //Before Java  SE 1.5 or Without Generics
     List<Type> list = new ArrayList<Type>();  //  With Generics

Let's take some of the important aspects of Generics:

Primitive type parameters NOT allowed in Generics

This is one of the limitation of Generics; compiler doesn't allows you to use primitives as type parameter. Instead of primitives you need to use corresponding boxed type as shown below:

        List<int> c1 = new ArrayList<int>();   //Not allowed; doesn't compile
        List<Integer> c2 = new ArrayList<Integer>();  //perfect

So you can't declare containers of primitive but does it mean you can't even store/retrieve primitives ? Certainly NOT.  Another Java SE5 feature comes to rescue.  Through autoboxing and autounboxing; you can easily add/retrieve primitive types.

         c2.get(0);   //add int
         int i = c2.get(i):  //retrieve value to primitive type
 

Why Generics ?

Before Generics came into picture; Containers were used without type parameter. So you could put any data type in the list. But you need to cast when you retrieve data from container/collection (as shown below) :

public static void main(String[] args) {
        List list = new ArrayList();
        list.add("abc");
        list.add(420);
        list.add("NaN");
       
        String s = (String) list.get(0);
        int i = (Integer)list.get(1);
       
        System.out.println(s + "  "+ i);
       
        //Integer nan = (Integer)list.get(2);  throws ClassCastException
    } 
Above code compiles properly but if you try to read data to an incompatible type; a runtime ClassCastException is thrown. Ideally it's better to get error as early as possible; preferably at compile time. So Generics got introduced to provide compile time (type) safety and eliminate the need for cast.

 

Generics Implementation

Before Generics introduction to the language, lots of code existed with raw type i.e. no type parameter. Challenge before designers was to add Generics feature in such a way that existing code remains legal and interoperable with the new changes. 

     List list = new ArrayList();   //before Java 5
     List<E> list = new ArrayList<E>();  // Java 5+

So first case still compiles on Java SE5+; though you will get warning.
Obvious questions are :  how is it possible ?  And how both can co-exist ?                                                                              
This is possible because Generics are implemented by erasure. This means that type constraint is enforced only at compile time and at run time the type information is erased or discarded. The .class file which gets generated as outcome of compiling will have same byte code for above 2 cases (i.e with raw and generics). 

To verify same; analyze the content of .class file for above 2 lines inside a main method.  javap utility inside bin directory can be used to analyze content of compiled file.

 

Generics vs Array

Generics and Array vary in couple of way. First, arrays are covariant but Generics by contrast are invariant. This point is shown in below diagram. Integer extends Number class so do the corresponding array classes; but the same is not true in case of Generics. 

Another important difference is that arrays are reified (knows type at runtime) but Generics are implemented by erasure( type information erased at run time).

Let's see an example to understand the same:

  public static void main(String[] args) {      
        Number[] array = new Integer[5];
        array[0] = 5;
        array[1] = 6.5;  //Runtime Exception
          
        List<Number> list = new ArrayList<Integer>();  //Doesn't compile
    }


 Above code has 2 problems:
  1. array object allows addition of a Number (6.5) at compile time but at run time it throws java.lang.ArrayStoreException. This is because run time type of array object is Integer[] not Number[] or Double[].
  2. list object doesn't allow declaration as arraylist of Integer. It gives compilation error Type mismatch: cannot convert from ArrayList<Integer> to List<Number> 

 So Array objects preserve the rules about the type of object they contain. So you can't abuse them. On the other hand Generics moves such error detection to compile time.    

 

Bounds/Wildcards

Advanced generics concepts are discussed in this separate post, here.

---
do post your feedback !!!

No comments:

Post a Comment