Table of contents
Open Table of contents
Introduction
The main objectives of Generics are to provide type safety and to resolve type casting problems.
Type safety
Arrays are type safe, i.e., we can give the guarantee for the type of elements present inside array. For example, if our programming requirement is to hold only String type of objects, we can choose String array. By mistake if we are trying to add any other type of object we will get compile time error.
String[] s = new String[10000];
s[0] = "Durga";
s[1] = "Ravi";
s[2] = new Integer(10);
/*
* CE: Incompatible types
* found: java.lang.Integer
* required: java.lang.String
*/
s[2] = "Shiva";
Hence, String array can contain only String type of objects. Due to this we can give the guarantee for the type of elements present inside array, hence arrays are safe to use with respect to types ,i.e., arrays are type safe.
But Collections are not type-safe, i.e., we can’t give the guarantee for the type of elements present inside collection.
For example, if our programming requirement is to hold only string type of objects and if we choose ArrayList, by mistake if we are trying to add any other type of object we won’t get any compile time error but the program may fail at runtime.
ArrayList l = new ArrayList();
l.add("Durga");
l.add("Ravi");
l.add(new Integer(10));
String name1 = (String)l.get(0);
String name2 = (String)l.get(1);
String name3 = (String)l.get(2); // RuntimeException: ClassCastException
Hence, we can’t give the guarantee for the type of elements present inside collection. Due to this, collections are not safe to use with respect to type, i.e., collections are not type-safe.
Q:: The question that arises is that, if we already have type safety in arrays then why is there a need to introduce a new concept of generics?
The problem with arrays is that they are fixed in size and you must know the size beforehand. But if you don’t know the size in advance and you still want type safety then there should be some new concept introduced. That’s why generics was introduced, to provide type safety for collections.
Type-Casting
In the case of arrays, at the time of retrieval it is not required to perform type casting because there is a guarantee for the type of elements present inside array.
String[] s = new String[10000];
s[0] = "Durga";
String name1 = s[0]; // Type casting not required
But in the case of collections, at the time of retrieval compulsory we should perform type casting because there is no guarantee for the type of elements present inside collection.
ArayList list = new ArrayList();
l.add("Durga");
String name1 = l.get(0); // CE: Incompatible types; Found: j.l.Object; Required: j.l.String
String name2 = (String)l.get(0); // Type casting is required
Hence type casting is a bigger headache in collections
To overcome above problems of collections SUN people introduced generics concept in 1.5 version of Java.
Hence the main objectives of Generics are:
- To provide type safety
- To resolve type casting problems
Generics
For example, to hold only String type of objects we can create generic version of ArrayList object as follows: ArrayList<String> l = new ArrayList<String(); For this ArrayList we can add only String type of objects, by mistake if we are trying to add any other type then we will get compile time error.
l.add("Durga");
l.add("Mayank");
l.add(new Integer(10)); // Compile time error
l.add("Shiva");
Hence, through generics we are getting type safety.
At the time of retrieval we are not required to perform type casting.
ArrayList<String> l = new ArrayList<String>();
l.add("Durga");
String name = l.get(0); // Type casting not required as there is guarantee it will only contain String object
Hence through generics we can solve type casting problem.
Normal ArrayList vs Generic version of ArrayList
| ArrayList l = new ArrayList(); | ArrayList<String> l = new ArrayList<String>(); |
|---|---|
| It is a non-generic version of ArrayList object | It is a generic version of ArrayList object |
| For this ArrayList we can add any type of object and hence it is not type safe | For this ArrayList we can add only String type of objects, and hence it is type safe |
| At the time of retrieval compulsory type casting is required | At the time of retrieval we are not required to perform type casting |
Important observation
-
Polymorphism concept applicable only for the base type but not for parameter type. Usage of parent reference to hold child object is the concept of polymorphism.
ArrayList<String> l = new ArrayList<String>(); List<String> l = new ArrayList<String>(); Collection<String> l = new ArrayList<String>(); // For parameter type?? ArrayList<Object> l = new ArrayList<String>(); // CE: incompatible types; Found: AL<String>; Required: AL<Object> -
For the type parameter we can provide any class or interface name but not primitives. If we are trying to provide primitive then we will get compile time error
ArrayList<int> l = new ArrayList<int>(); // CE: unexpected types; Found: int; Required: Reference
Internal Implementation
Until 1.4 version a non-generic version of ArrayList class is declared as follows:
Class ArrayList{
add(Objcet o);
Object get(int index);
}
The argument to add() method is Object and hence we can add any type of object to the ArrayList. Due to this, we are missing type safety.
The return type of get() method is Object, hence at the time of retrieval we have to perform type casting.
But in 1.5 version a generic version of ArrayList class is declared as follows:
class ArrayList<T> {
// T = Type Parameter
add(T t);
T get(int index);
}
Based on our runtime requirement, T will be replaced with our provided type.
For example: To hold only String type of objects, a generic version of ArrayList object can be created as follows ArrayList<String> l = new ArrayList<String>();. For this requirement, compiler considered version of ArrayList class is as follows:
class ArrayList<String> {
add(String t);
String get(int index);
}
The argument to add() method is String type, hence we can add only String type of objects. By mistake if we are trying to add any other type then we will get compile time error.
ArrayList<String> l = new ArrayList<String>();
l.add("Durga");
l.add(new Integer(10));
/*
* CE: cannot find symbol
* symbol: method add(j.l.Integer)
* location: class ArrayList<String>
*/
l.add("Mayank");
Hence through Generics we are getting type safety.
The return type of get() method is String and hence at the time of retrieval we are not required to perform type casting String name1 = l.get(0);
Generic Class
In Generics, we are associating a type parameter to the class, such type of parameterized classes are nothing but Generic classes or Template classes.
Based on our requirement we can define our own generic classes also:
class Account<T> {
}
Account<Gold> gold = new Account<Gold>();
Account<Platinum> platinum = new Account<Platinum>();
class GenDemo<T> {
T obj;
GenDemo(T obj) {
this.obj = obj;
}
public void show() {
SOUT("The type of obj is: " + obj.getClass().getName());
}
public T getObj() {
return obj;
}
}
class Test{
main() {
GenDemo<String> obj1 = new GenDemo<String>("Hello");
obj1.show(); // The type of obj is: j.l.String
SOUT(obj1.getObj()); // Hello
GenDemo<Integer> obj2 = new GenDemo<Integer>(10);
obj2.show(); // The type of obj is: j.l.Integer
SOUT(obj2.getObj()); // 10
GenDemo<Double> obj3 = new GenDemo<Double>(10.5);
obj3.show(); // The type of obj is: j.l.Double
SOUT(obj3.getObj()); // 10.5
}
}
Bounded Types
We can bound the type parameter for a particular range by using extends keyword, such types are called bounded types.
class Test<T>{}
As the type parameter we can pass any type and there are no restrictions and hence it is unbounded type:
Test<Integer> t1 = new Test<Integer>();
Test<String> t2 = new Test<String>();
But what if there are methods defined in Test class like:
multiply(T a, T b) {
return a*b;
}
add(T a, T b) {
return a + b;
}
divide(T a, T b) {
return a / b;
}
Then there will arise runtime exception when we use String as type parameter.
To overcome this, and to allow only specific classes as type parameters, bounded types comes into play.
Syntax for bounded type:
class Test<T extends X>{}
Xcan be either class or interface.- If
Xis a class then as the type parameter we can pass eitherXtype or it’s child classes. - If
Xis an interface then as the type parameter we can pass eitherXtype or it’s implementation classes.
Are these valid?
class Test<T implements X>: NO! No such concept of usingimplementskeyword for bounded type parameters. But, you can replaceimplementswithextendsand use interface and it’s implementation classes as type parameters.class Test<T super X>: NO! There is no such concept of usingsuperkeyword in bounded type parameters.
Tricky Examples
Example1:
class Test<T extends Number> {}
Test<Integer> t1 = new Test<Integer>();
Test<String> t2 = new Test<String>(); // Compile time error
/*
* Compile time error: Type parameter is j.l.String is not withing its bound
*/
Example2:
class Test<T extends Runnable> {}
Test<Runnable> t1 = new Test<Runnable>();
Test<Thread> t2 = new Test<Thread>();
Test<Integer> t2 = new Test<Integer>(); // Compile time error
/*
* Compile time error: Type parameter is j.l.Integer is not withing its bound
*/
We can define bounded types even in combination also, example:
class Test<T extends Number & Runnable>{
}
As the type parameter we can take anything which should be child class of Number and should implement Runnable interface.
Example 3:
class Test<T extends Number & Serializable>{}
Test<Integer> t = new Test<Integer>(); // --> Valid
Test<Float> t = new Test<Float>(); // --> Valid
Test<String> t = new Test<String>(); // --> Invalid.
Here Test class is able to allow the elements which must be either same as Number or sub classes to the Number, and which must be the implementations of Serializable interface.
Example 4: Two classes at same time
class Test<T extends Number & Thread>{}
Status: Invalid Reason: extends keyword will not allow two class types at a time.
Example 5:
class Test<T extends Runnable & Serializable>{}
Statis: valid. Reason: extends keyword is able to allow more than one interface type.
Example 6:
class Test<T extends Number & Runnable>{}
Status: Valid. Reason: Extends is able to allow both class type and interface type, but, first we have to specify class type then we have to specify Interface type.
Example 7:
class Test<T extends Number & Runnable>{}
Status: Invalid. Reason: extends keyword allows first class_Name then interface_Name
Source
- DurgaSoft