Tuesday, April 13, 2010

Generics, Polymorphism, extends Confusion

Dear All,

I decided to write this article, because i had a discussion over java-ranch about this topic with a person. As being asked for a detailed example, i thought, i will write a whole article about it. I am new to generics as well. To my understanding, i will try to explain best to my capability.

Okay, first i will take a quick reference of an example from Kathy and Sierra book published in the SCJP guide. Here it goes.

package collections;

import java.util.ArrayList;
import java.util.List;

class Animal{
 
 public void test(){
  System.out.println("I am Animal");
 }
 
}

class Dog extends Animal{
 public void test(){
  System.out.println("I am Dog");
 }
}

class Cat extends Animal{
 public void test(){
  System.out.println("I am Cat");
 }
}


public class AnimalTest {
 public void addAnimal(Animal[] animalArray){
  for (Animal animal : animalArray) {
   animal.test();
  }
 }
 
 public static void main(String[] args) {
  
  
  AnimalTest animalTest = new AnimalTest();
  Dog[] d = {new Dog(), new Dog(), new Dog()};
  animalTest.addAnimal(d);
  
}

}


Now as we can see above, that this behavior is common for arrays. That any method which has declared a super-type reference can accept any array of its sub-type which is shown above. Now we are thinking, do collections behave the same way?

Well, if we amend the above example and overload the addAnimal method which accepts a list of type animal

public void addAnimal(List<Animal> animalArray){
  for (Animal animal : animalArray) {
   animal.test();
  }
}

public static void main(String[] args) {
  
  
  AnimalTest animalTest = new AnimalTest();
  Dog[] d = {new Dog(), new Dog(), new Dog()};
  animalTest.addAnimal(d);
  
 List<animal> animalList = new ArrayList<animal>();
  animalTest.addAnimal(animalList);
  
  
 }

It will work fine. But if we try to achieve the same polymorphic behavior which we were getting in case of arrays. What happens? Lets amend the above main method slightly like this

public static void main(String[] args) {
  
  
  AnimalTest animalTest = new AnimalTest();
  Dog[] d = {new Dog(), new Dog(), new Dog()};
  animalTest.addAnimal(d);
  
 List<animal> animalList = new ArrayList<animal>();
  animalTest.addAnimal(animalList);
  
  List<dog> dogList = new ArrayList<dog>();
  animalTest.addAnimal(dogList);
 }

Do you think it will compile? Woops, the method just blows up and says, I am sorry, there is no method defined in the AnimalTest class which takes a list type of Dog. Now here we can't get the same polymorphic behavior as we used to take as in Arrays.

We will discuss why in a minute. First question is this, what can be the possible solution?
One solution is to overload the addAnimal() method for every animal type, like Dog, Cat. The animal hierarchy is not limited to only Dog and Cat. Tomorrow the design will enhance and a hell lot other Animals can come in, we can't overload the addAnimal() method for every possible animal type.

So you must be thinking, what is the work-around of this situation?

Well, generics gives us a way and that is called a wild-card approach. If we have to achieve this poly-morphic behavior for Collection, generics gives us this way.

? extends

So what if we change the above method as this

public void addAnimal(List<? extends Animal> animalArray){
  for (Animal animal : animalArray) {
   animal.test();
  }
}

Oh great, go and compile in your IDE, now your class will start compiling and won't complain that there is no method defined for list of type Dog.

Ok so far so good, but this has a potential limitation. Before coming to that, lets refer back to our array version. What if we change the Array version of addAnimal like this

public void addAnimal(Animal[] animalArray){
  animalArray[2] = new Cat();
 }

Oops!! There is something highly wrong happening. We passed a Dog Array but when it came inside the method, it just added a Cat object. Now what will happen? No worries, at run-time when you try adding something other then Dog, arrays have an exception which is called ArrayStore exception, it will be thrown and you won't be able to add the wrong-type into the Array.

Does it work the same way for Generics?

Well, just refer back, i just told that the above method which was taking a List has a potential limitation. Yes surely, you did achieve the polymorphic behavior by the ? extends thing, but if you try to do something like this inside the method

public void addAnimal(List<? extends Animal> animalList){
  animalList.add(new Cat());
  animalList.add(new Dog())
}

It won't compile. Why? It won't let you add anything inside the animalList. But why?

Good question. If you have read generics, then you must be knowing that generics only gives us compile time safety. It means that once the compiler makes sure that, everything is perfectly fine, and coverts it into byte-code, it removes the type information. It is called type-erasure.
It can be clear from this sample,

//Pre Java 5 version
List animalList = new ArrayList();
List stringList = new ArrayList();

//It welcomes anything inside.
animalList.add(new Dog());
animalList.add(new Human());

//It welcomes anything inside.
stringList.add(new Dog());
stringList.add(new Human());
stringList.add(42);


//Java 6 style
List animalList = new ArrayList();
//Compiler screams.Shutup you can't add anything except animal
animalList.add(new Dog());
animalList.add(new Human());

List stringList = new ArrayList();
//Compiler screams.Shutup you can't add anything except String
stringList.add(new Dog());
stringList.add(new Human());

Before Java-5, inside the collections, you were able to add anything. As we just saw above. In java 5 or 6, compiler restricts you at compile. But after verifying everything, it removes the type information from byte code and at run-time for JVM, the List of type animal will be same as pre java 5 List. It will allow you to add anything. It can't throw any exception even, because type information doesn't exist at run-time in generics. So what will be the disadvantage?

We will just write one snippet of code, and tell. Lets say, the above method let you add anything inside the list, then you can add anything, like Dog, Cat, Parrot anything. Then lets say you had to iterate over the elements in the collection.

Dog d = animalList.get(0);

What it says? I am sure, the compiler made it sure, no wrong thing is added, so i can just take out the objects inside the array which i know is Dog. But.. What happened? You added a Cat object and once you put that out, you are dead. Woops!! Wrong thing got added and JVM didn't stop me to add wrong thing, why, because it has no type information and someone added wrong thing inside. So in the above ? extends approach, compiler restricts you to add anything inside the collection and just provides a polymorphic behavior and treats the collection as read-only collection.

Okay question is, Is there anyway we can add something to list, but still it should be safe. Yes.

public void addAnimal(List<? super Dog> animalList){
  animalList.add(new Dog());
 }

By such a version, you are saying accept any list who is parent of Dog or even Dog itself. It says, you can add anything which is of type Dog or sub-type of Dog, but nothing other then Dog or its sub-type. How we are adding in safe-way? Here there are three options

1- You can get a list of type Animal.
2- You can get a list of type Object
3- You can get a list of type Dog

How is the safe add operation achieved?

If we get an animalList, of course we can add a Dog to it. If we get Object list, we can again add Dog to it or any of its sub-type because Object is the super-type of Dog. We can safely add even if we get a Dog list. That's how addition can be done in a safe-way.

Hope i am able to clear some of the confusion regarding how polymorphism is achieved in Collections and why it is different from Arrays. Any comments and amendments are welcome.

Enjoy,
Reevs

1 comment:

  1. fantastic post man, you have indeed covered the generics with good detail with code examples. you can also include about writing type-safe classes and interfaces. by the way I have also blogged my experience as 10 points on generics in java let me know how do you find it.

    Javin

    ReplyDelete