Monday 11 November 2013

How does Polymorphism works with Generics References.

In this tutorial we will see what happens when you apply the concepts of Polymorphism to Generics.

Just to brush up few concepts.
What is Polymorphism?
It is the ability of an object to take many forms. The most common use of Polymorphism occurs when a superclass reference points to a subclass object.

Example 1.
class Parent{
public void sayHello(){
System.out.println("Hello Parent");
}
}
class Child extends Parent{
public void sayHello(){
System.out.println("Hello Child");
}
}
public class Test2 {
         public static void main(String[] args) {
       Parent ref = new Child();                                                          //Line 1.
ref.sayHello();                                                                        //Line 2.
         }
}
Output: 
Hello Child

Notice how at line 1 the reference variable "ref" of type Parent (supertype or superclass type) is used to point to an object of type Child (subtype or subclass type). 
See how at line 2 when sayHello method is invoked, the JVM figures out what object the reference (ref) is pointing to. ref is pointing to Child() object and hence sayHello() method of Child is invoked. 
Although polymorphism is not just limited to this scenario this is just a common situation encountered in Java Programming.

What is Generics?
Generics is a feature introduced in Java 5 which allows a type or method to operate on a type while providing compile time safety.

Example 2.
ArrayList<String> myList = new ArrayList<String>();
myList.add(new Integer(1));                                                                  //Line 1-throws a compile time error
myList.add("John");                                                                               //Line 2-works perfectly fine.

The declaration ArrayList<String> myList = new ArrayList<String>() says that myList can only hold objects of type String. If you add any other type of objects to the list. The compiler will throw an error.

You must be wondering why do you want to add a single type of object to myList. Here is the example.

Example 3.
public class Test2 {
         public static void main(String[] args) {
                    List list = new ArrayList();
                    list.add(new Integer(2));
                    String name = (String)list.get(0);
         }
}
Output:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

Here is the explanation.
Imagine we are not using generics. A list can hold any object. When you read the contents of the list you will always get an Object type.
public Object get(int index)      ->Signature of get method.
 So you will have to typecast it.
String name = (String)myList.get(0);
Since in this example we are not using generics we could add even an Integer object to List. 
So myList.get(0) would return an Integer. An Integer typecasted to String would give you a ClassCastException.

Huff Enough of revision. Lets get to the point of this tutorial. What happens when you apply the concepts of Polymorphism to Generics.


As we have seen in Example 1, Line 1
Parent ref = new Child();      
is perfectly OK. i.e. a parent class reference can point to a subclass object.

So what about this
List myList<Object> = new ArrayList<String>();
List myList<Number> = new ArrayList<Integer>();

This should work fine isn't it?
But IT DOESN'T.

Burn into your brain a rule of generics.
"Generic type of the reference and the generic type of the 
object must be exactly identical"
Polymorphism applies only to the base type (in this example, the collections List and ArrayList).
If the generic type of the reference is Number then it can point to only objects whose generic type is Number, neither a subtype of Number nor a supertype of Number. Only and only Number.

So the following lines are perfectly fine.
List myList<Object> = new ArrayList<Object>();                                          //Fine
List myList<Number> = new ArrayList<Number>();                                     //Fine
Set mySet<Parent> = new HashSet<Parent>();                                              //Fine

When working with generics type a cast is not required when extracting elements form  the collection. While for non-generic types an explicit cast is required.

Example 4.
List<String> myList = new ArrayList<String>();
myList.add("Vicky");
myList.add("John");
String name = myList.get(0);                                                                //No cast was required

List list = new ArrayList();
list.add("Sunny");
name = (String)list.get(0);                                                                     //An explicit cast was required.

In Example 4 we were dealing with Strings, now what would happen if we were dealing with Integers. One would expect that Unboxing would be done for us even when using Pre-Java 5 non type safe code and we won't have to type cast. But that is WRONG. Autoboxing only converts from a Wrapper class(Integer or Long or Short etc.) to a primitive. It cannot convert from an Object type to a primitive.
Following example illustrates it.

Example 5.
List<Integer> listOfTypeSafeIntegers = new ArrayList<Integer>();
/*Declaring type safe list of Integers. You can only add Integers to it.*/                                              listOfTypeSafeIntegers.add(100);
listOfTypeSafeIntegers.add(200);
listOfTypeSafeIntegers.add(300);
i = listOfTypeSafeIntegers.get(0);      
/*Typecast not necessary as get(int) method returns an Integer. The compiler automatically performs the typecasting for us such that get(int) returns Integer instead of object. Unboxing converts that Integer wrapper to int.*/

List listOfIntegers= new ArrayList();       //Declaring raw list.Can contain any objects
listOfIntegers.add(100);                                                              
listOfIntegers.add(200);
listOfIntegers.add(300);
int i = (Integer)listOfIntegers.get(2);       
/*Typecast is necessary as get() method returns an Object and an Object cannot be unboxed to primitive*/

By now you might have become pretty much comfortable with Generics. We have seen its introduction, basic syntax, how it works with polymorphism and unboxing.
Few catches when declaring type safe code.
In the following example I am declaring reference of generic type Integer and the object as a raw type.
List<Integer> list = new ArrayList();   
//This line pops up a warning- Array List a raw type references to generic type ArrayList<> should //be parameterized. 

list.add(100);                //OK. I can add an Integer to it.
lists.add(new String());  //When I try to add a string compiler throws an error. 

From this we infer that even if reference is declared of a generic type say Integer, compiler restricts us from adding any other type say String.

So will I get an error at Line 1 in the below example?
List list = new ArrayList<Integer>();
list.add(100);

list.add(new String());    //Line 1

Strangely, there is no error. Compiler just sees the generic type of the reference. Since it was a raw type compiler assumes any type of object can be added to the list.

That's it in today's tutorial. Signing off for here. Will be back with a whole lot of new tutorials for you all. All doubts, suggestions for improvements, correction of mistakes are welcome. Hope this helps. Happy Learning!!!

No comments:

Post a Comment