Go to >> Java Tutorials
Target audience: Beginners
Lets start with a simple example with normal java code
import java.util.ArrayList;
import java.util.List;
public class FirstExample {
public static void main(String[] args) {
List testList = new ArrayList();
testList.add(new Integer(6));
String string = (String) testList.get(0);
}
}
The above class compiles perfectly, but at run-time... Bang! Throws exception
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer
at com.learner.generics.FirstExample.main(FirstExample.java:24)
If we could caught this exception at compile time our application would be very safe at run-time. This is where Generics come to play.
Generics makes sure the classes are type safe at compile time when those classes are working with arbitrary objects. The generics are only used at compile time .The generic code that are written will be translated ( type erased ) into the normal code while compiling. So we’ll say it again... “ Generics makes sure the classes are type safe at compile time”
Now I rewrite the code with Generics.
import java.util.ArrayList;
import java.util.List;
public class FirstExample {
public static void main(String[] args) {
List<Integer> testList = new ArrayList<Integer> ();
testList.add(new Integer(6));
String string = (String)testList.get(0); //(1)
}
}
When I write the code (1), the compiler would shout at me; hey” can not cast from Integer to String”. So I can be more careful when I write code and I will be safe. Thanks to generics!
Generics are mostly used with Collections. Collections deal with objects which are java.lang.Object type. When we save any object to Collections it saves as java.lang.Object type and it returns as such. So a casting is needed when we get it out. Generics solve this casting issue. See the following example.
import java.util.ArrayList;
import java.util.List;
public class FirstExample {
public static void main(String[] args) {
List<Integer> testList = new ArrayList<Integer>();
testList.add(new Integer(6));
Integer intValue = testList.get(0);
}
}
Simple as that!. Now we’ll look into the types of Generics and how to define Generic classes.
Generics and Classes/Interfaces
When a class is defined with the parameter (or parameters) then it’s called as Parameterized class. It’s another name for generic class. The parameter is called as Type parameter. This is applicable for Interface too.
class CheckArrayList<E> extends ArrayList<E> {
// implementation goes here.
}
In then above code, CheckArrayList is parameterized with one parameter type ‘E’’ but it can have more than one parameter. Type parameter is an unqualified identifier that is used as a place holder .This will be replaced by the compiler with the specified Type when an object is created for the generic class. In bellow, the Type parameter E is replaced with Integer.
CheckArrayList <Integer> testList = new CheckArrayList<Integer> ();
When it comes in hierarchies of generic classes, one rule applies. A class or Type parameter can not have two different parameterization of the same class or interface at the same time. See the following example.
interface MyInterface<T> {
// Implementation goes here.
}
class Second<T> implements MyInterface<T> {
// Implementation goes here.
}
class Third<T> extends Second<Integer> implements MyInterface<String> {
// Implementation goes here.
}
Here, the compiler will give you an error you saying “The interface MyInterface cannot be implemented more than once with different arguments “.
The correct way would be
class Third<T> extends Second<String> implements MyInterface<String> {
// Implementation goes here.
}
Generics and Methods
Generics can be used in methods too. The generic type is declared after all the modifiers of the Class and right before the return type. See the following example
public <E> get(int index) {
// Implementation goes here.
return elementData[index];
}
The syntax is same as generic class. Two methods can not have same name and argument types if they have then the compiler will give an error.
Generics and Arrays
You can not create a array of generic type and arrays of type variable. The following code will generate a compile time error
Vector<Integer> vector [] = new Vector<Integer>[10];
A workaround is to construct an array of objects with a type-cast:
Vector<Integer> vector [] = (Vector<Integer>[]) new Object[10];
But this is not a good idea to use in this way
Raw Types and Type erasure
Now lets see about Raw types. What are Raw types?
Raw types are the one without its type parameters. MyInterface
When a generic type is instantiated, the compiler translates those types by a technique called 'type erasure' which the compiler removes all information related to type parameters and type arguments within a class or method. In other words the compiler translates into normal java code (without Generics). Raw types are used for legacy codes that use non generic codes.
Generics and Exceptions
Generics can be used for exception in throws clause. But java doesn’t allow to be used in catch clauses. See the following example where the Generics is used in throws clause.
package com.learner.generics;
import java.io.IOException;
public class SecondExample<V> {
public void sample() throws V {
// Implementation goes here.
}
public static void main(String[] args) {
SecondExample<IOException> example = new SecondExample<IOException>();
try {
example.sample();
} catch (IOException e) {
e.printStackTrace();
}
}
}
As I stated above java doesn’t allow to be used in catch clauses. The below is not allowed
try {
//Implementation goes here.
} catch (MyException<Integer> e) {
//Implementation goes here.
} catch (MyException<String> e) {
//Implementation goes here.
}
Because the type is erased at the compile time, in other words, the generics code will be translated into normal code and the types will be removed. After the translation MyException