Generics was a boon to a wondering developer, it gives us compile time checks to ensure we are actually inserting a predeclared type into the collection, also no casting is required.

But there could be a possibility of adding any kind or type of object into a predeclared type collection is still possible. And there is a good reason why. This article will discuss about it.

Having any kind of object into a predeclared type collection is possible , which is quite simply really.

import java.util.ArrayList;

class Animal {
	public void eat()  {
		System.out.println("I eat");
	}
}
public class Test {
	public static void main(String [] args) throws Exception {
		ArrayList<Object> al = new ArrayList<Object>();
		al.add("Test String");
		al.add(new Animal());
	}
}

As you can see in the above code , we can add a String and an Animal into the typed collection ArrayList, with Object as a type. No compilation problem will be reported. But the question is, Is this kind of code works  ? The answer is Yes and No.

No: When you declare your collection with type more super , it induces following problems, Let see in the context of the above code.

  1. When you actually retrieve the items, you can cast the object to its original object , for example Animal a =( Animal)al.get(1) ; but you looses the flexibility , and could end up in ClassCastException at runtime if you don't know which object is added at what index position of the arraylist , thus this fails.
  2. Knowing the above , you decided to keep it like Object ob=al.get(1); will return you the Animal object , but referred by Object , which slashes the possibility of calling animal method. like ob.eat() ; would give you compilation error.

Now we know the problem , this kind of coding especially with very Super classes as type is a big problem, and moreover Generics is all about eradicating casting, and not getting  suprising ClassCastException.

But Yes: When you have a very well defined object heirarchy , it helps you to execute the common behavior just fine, Lets take the following example to clear you.

import java.util.ArrayList;

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

class Dog extends Animal{
	// eat inherited
	
	public void bark(){
		System.out.println("I am barking");
	}
	public String toString(){
		return "Dog ";
	}
}
class Cat extends Animal{
	//eat inherited
	
	public void meow(){
		System.out.println("Meow , give me some milk");
	}
	public String toString(){
		return "Cat ";
	}
}
public class Test {
	public static void main(String [] args) throws Exception {
		ArrayList<Animal> al = new ArrayList<Animal>();
		al.add(new Dog());
		al.add(new Cat());
		for(Animal a:al){
		System.out.print(a.toString());
		a.eat();
		}
	}
}


As you can see what we have this time is the ArrayList is now typed to Animal, And we have Dog and Cat extends the Animal , thus inheriting the eat behaviror. The Dog and Cat has also additional methods. Now in the ArrayList ,I have added Dog and a Cat object , which finally I am iterating and calling the eat method , which is common to all. Wonder if you were to perform the same operation on the entire list of Animals.

Let me know your thoughts.