Saturday, 4 May 2013

Understanding Generics in Java

When you talk about Collections, Generics are automatically introduced. After knowing basics of how Collections work let us explore the World of Generics.



Before we proceed to even understanding what generics is I want you remember one thing

                             "Generics is a Compile Time Concept"


By Generics you define what data type is associated with your Generic Class or a method. So at compile time when you do operations on your class or method compiler will always expect that generic type. Lets take an example to understand this better.
   Lets say you have a List to store name of some people. This is how you do it.

List nameList = new ArrayList();
nameList.add("John");   // 0th index
nameList.add("Sam") ;  // 1st index
etc

So far things look good. We created a List and we added some names to it. But now say I do something like below -
nameList.add(23);  // 2nd index

Notice here 23 is not a String but an integer. Even if some one has a name called 23 we must say "23". But the point to note here is that one can add anything to the List at this point of time. So far so good. Code will Compile and even run.

Lets see where the problem might occur.
String name1 = (String) nameList.get(0);
String name2 = (String) nameList.get(1);
String name3 = (String) nameList.get(2);  //Oops Error!

because 23 is a int and you can't type cast it into a String. Also note that even if you store and actual String every time we have to type cast it . Wondering if there is a better alternative ?

This is why we use Generics.

Let rewrite above example - 
List <String>nameList = new ArrayList<String>();
nameList.add("John");   // 0th index
nameList.add("Sam") ;  // 1st index

All good. Now lets add an integer -

nameList.add(23);  // 2nd index

At this point compiler will give error. Compiler know nameList can only have String and cannot accept integer . Program will not compile so running program will never come into picture. That's why we say generics is used for compile time safety.

Now lets extract valued from the List

String name1 = nameList.get(0);
String name2 = nameList.get(1);

That's right! No need to type cast. Compiler will know that nameList only contains String and hence it will always return a String.Lets revise benefits of using Generics -

Why Generics?

  • Stronger type checks at Compile time :

    A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find.
  • Elimination of need to cast :

    The following code snippet without generics requires casting - 

    List list = new ArrayList();
    list.add("hello");
    String s = (String) list.get(0);
    
    
    When re-written to use generics, the code does not require casting - 

    List<String> list = new ArrayList<String>();
    list.add("hello");
    String s = list.get(0);   // no cast
    
    
  • Enabling programmers to implement generic algorithms :

    By using generics, programmers can implement generic algorithms that work on collections of different types, can be customized, and are type safe and easier to read.
 Note :  Generics is not supported by primitive Data types. So you cannot do something like List<int> myList  = new ArrayList<int>();. You will have to do List<Integer> myList  = new ArrayList<Integer>();


Creating Generic Classes

 So far we saw use of generics on existing classes that JDK provides like List. Now lets write generic class of our own.


 I am going to create my own generic data structure called MyDataStructure. Code is as follows - 

public class MyDataStructure<T> {
    
    T data;

    public T getData() {
        return data;
    }

    public void putData(T data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "MyDataStructure [data=" + data + "]";
    }
}

Notice that T. It's called type place holder - the type name T within angle brackets “<” and “>”. We use the same in all places in subsequent code. Now lets test this class.


public class TestGenerics {

    

    public static void main(String args[]) {

        MyDataStructure<String> myStringDataStructure = new MyDataStructure<String>();

        myStringDataStructure.putData("OpenSourceForGeeks");

        System.out.println(myStringDataStructure);

        MyDataStructure<Integer> myIntegerDataStructure = new MyDataStructure<Integer>();

        myIntegerDataStructure.putData(21);

        System.out.println(myIntegerDataStructure);

    }



}


and the output is : 
MyDataStructure [data=OpenSourceForGeeks]
MyDataStructure [data=21]

Diamond Syntax

MyDataStructure<String> myStringDataStructure = new MyDataStructure<String>();

Noticed how we have to provide same type parameters in both the declaration type
(MyDataStructure<String>) and the new object creation expression (new MyDataStructure<String>())

To simplify this diamond syntax is introduced in Java 7. In this type parameter in new Object creation expression can be omitted. Compiler infers the type from the declaration part.


So you can simply say
MyDataStructure<String> myStringDataStructure = new MyDataStructure<>();


Type Erasure

If you recollect I had mentioned in the beginning of this post that generics is compile time concept. This is also called type erasure. After compilation all generic code is replaced by concrete type. At Runtime there is no generics involved. 

Knowing this fact try to answer following question. What would be the output -

public static void main(String []args) {
    List<Integer> integerList = new LinkedList<Integer>();
    List<Double> doubleList = new LinkedList<Double>();
    System.out.println("integerList class: " + integerList.getClass());
    System.out.println("doubleList class:" + doubleList.getClass());
}

Output : 

integerList class: class java.util.LinkedList
doubleList class:class java.util.LinkedList

Reason : 

At runtime there are no generics. It's plain concrete classes that exist. To sum this up due to type erasure after compilation both types are treated as same LinkedList type.

Important points

  • It’s possible to define or declare generic methods in an interface or a class even if the class or
    the interface itself is not generic.
  • You cannot instantiate a generic type using new operator. Eg you cannot do -

    T data = new T(); or
    T[] data = new T[10];


    Also you cannot create arrays of generic classes.

    List<Integer>[] array = new List<Integer>[10]; // does not compile

    Of course you can do -

    List[] array = new List[10]; // compiles

    but not with generics

    For reference see ArrayList code. There we have

    public class ArrayList<E> extends.......
    and

    data structure in it is

    private transient Object[] elementData; // note no generics here

    Then add method is like

    public boolean add(E e) {
        ensureCapacity(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
  • Also note Generics does not support covariance or subtyping. Meaning you cannot do something like -

    List<Number> numbers = new ArrayList<Integer>(); // will not compile

Related Links 



No comments:

Post a Comment

t> UA-39527780-1 back to top