Pages

Tuesday 17 September 2019

Generic concept

Agenda
Introduction
The generic concept is introduced in Java 1.5v
To resolve two problems of collection.
Type safety
ClassCastException
ArrayList list = new ArrayList();//Non generic a.k.a raw type version till java 1.4
ArrayList<String> list = new ArrayList<String>();//Generic Type
In Java 1.7 diamond operator <> is introuduce to improve code readability
ArrayList<String> list = new ArrayList<>();//Generic Type,valid from java 1.7 onwards
Here you can't add elements other than String type othewise you will get compilation error.
Example:
list.add("Anu");//valid
list.add(10);// invalid
Note1: Polymorphism concept is applicable only for the base type,not for parameter type.
Note2: Parameter type must not be primitive types. It should be any class or interface
Example
ArrayList<int> list = new ArrayList<>();// invalid

Generic Classes

Until Java 1.4v ArrayList class is defined as follows.
Example
class ArrayList{
public boolean add(Object o){}
public Object get(int index){}

}
add() method can take Object as the argument and hence we can add any type of the object to the ArrayList. Due to this, we are not getting type of safety.
The return type of get() method is object hence at the time of retrieval compulsory we should perform typecasting. But in Java 1.5v a generic version of ArrayList class is defined as follows.
Generic
Example: class ArrayList<E>{
public boolen add(E data){}
public E get(int index){}
}
Based on our requirement E will be replaced with our provided type.
To hold only String type of objects we can create ArrayList object as follow.
Example
ArrayList<String> list = new ArrayList<>():
For this requirement compiler considered ArrayList class is
Example
class ArrayList<String>{
public boolen add(String data){}
public String get(int index){}
}
add() method can take only String type as argument hence we can add only String type of objects to the List.Hence through generics, we are getting type safety.
At the time of retrieval, it is not required to perform any typecasting.

How can we create a custom generic class?
class Student<T>{}

Bonded Types
We can bound the type parameter for a particular range by using the extends keyword. such types are called bounded types.
Example
class Test<T>{}
Test<Integer> t1 = new Test<>();
Test<String> t2 = new Test<>();
As the type parameter we can pass any type there is no restriction hence it is unbounded type.
Example
class Test<T extends X>{}
If X is a class then as the type parameter we can pass either X or its child classes.
If X is an interface then as the type parameter we can pass either X or its implementation classes.
Example
class Test<T extends Number>{}
class Test1{
public static void main(String[] args){
Test<Integer> t1 = new Test<>();
Test<String> t1 = new Test<>();// Compilation Error
}
}
Example
class Test<T extends Runnable>{}
class Test1{
public static void main(String[] args){
Test<Thread> t1 = new Test<>();// Thread class is implementation class of Runnable interface.
Test<String> t1 = new Test<>();// Compilation Error
}
We can't define bounded types by using implements and super keyword.
Example
class Test<T implements Runnable>{}//invalid
class Test<T super String>{}//invalid
But implements keyword purpose we can replace with extends keyword.
As the type parameter, we can use any valid java identifier but it is recommended to use
T for Type
E for Element
K for Key
V for Value
Example
class Test<X>{}//valid
class Test<Anu>{}//valid
We can pass any number of type parameters, there is no restriction at all.
Example
class Map<K,V>{}
class Test<A,B,C,D,E....>{}
We can also define bounded types in combination.
Example
class Test<T extends Number & Runnable>{}//valid
As the type parameter, we can pass any type which extends Number class and implements Runnable interface.
class Test<T extends Number & String>{}// invalid, we can't extend more than one class at a time.
class Test<T extends Runnable & Comparable>{}//valid
class Test<T extends Runnable & Number>{}//invalid, we have to take 1st class followed by interface.

Generic methods and wild card character(?)
public void methodOne(ArrayList<String> list){
list.add("Anu");
list.add(null);
list.add(10);// invalid, only string type and null is allowed.}

public void methodOne(ArrayList<?> list){};We can use this method for ArrayList of any type but within method we can't
add anything except null.
Example
list.add(null);//valid
list.add("Anu");// invalid
list.add(10);//invalid
This method is useful whenever we are performing only read operation.

public void method(ArrayList<? extends X> list):
  • If X is a class then this method is applicable for ArrayList of either X type or its child classes.
  • If X is an interface then this method is applicable for ArrayList of either X type or its implementation classes.
  • Within the method we can add only null value.

public void methodOne(ArrayList<? super X> list):
  • If X is a class then this method is applicable for ArrayList of either X type or its super-types.
  • If X is an interface then this method is applicable for ArrayList of either X type or its super-types.
  • But within the method, we can add X type objects and null to the List.
  • If X is a class then within the method we can add only X type or its sub-types.
  • If X is an interface then within the method we can add only implementation classes of X.
Below example will help you to understand the above concept.
class A{}
class B extends A{}
class C extends B{}
class D extends C{}
interface X{}
interface Y extends X{}
class P  implements Q, Y{}
interface Q {}
class R extends P{}
  1. import java.util.ArrayList;
  2. import java.util.List;
  3. class GenericAtMethod {
  4. public static  void m1(List<? extends B> list) {
  5. // We can add only null value
  6. list.add(null);
  7. }
  8. public static  void m2(List<? extends Y> list) {
  9. //we can add only null value
  10. list.add(null);
  11. }
  12. public static  void m3(List<? super B> list) {
  13. // we can add only either B type or its sub-types(C,D) or null
  14. list.add(null);
  15. list.add(new C());
  16. list.add(new D());
  17. }
  18. public static  void m4(List<? super Y> list) {
  19. //we can add only either implementation classes of Y(P,R) or null
  20. list.add(null);
  21. list.add(new P());
  22. list.add(new R());
  23. }
  24. public static void main(String[] args) {
  25. //method m1() will accept only B type or its sub-types(C,D)
  26. List<A> l1= new ArrayList<>();
  27. List<B> l2= new ArrayList<>();
  28. List<C> l3=new ArrayList<>();
  29. List<D> l10=new ArrayList<>();
  30. List<Object> l9=new ArrayList<>();
  31. //m1(l1); invalid because A is super type of B
  32. m1(l2);
  33. m1(l3);
  34. m1(l10);
  35. //method m2() will accept only Y type or its implementation classes(P,R)
  36. List<Y> l4 = new ArrayList<>();
  37. List<P> l5 = new ArrayList<>();
  38. List<R> l6 = new ArrayList<>();
  39. List<X> l7 = new ArrayList<>();
  40. List<Q> l8 = new ArrayList<>();
  41. m2(l4);
  42. m2(l5);
  43. m2(l6);
  44. // m2(l7) invalid
  45. // m2(l8); invalid
  46. //method m3() will accept only List of either B type or its super-types(A,Object)
  47. m3(l1);
  48. m3(l2);
  49. m3(l9);
  50. //m3(l3); invalid
  51. //m3(l10); invalid
  52. //method m4() will accept only List of either Y type or its super types(Y,Object)
  53. m4(l4);
  54. m4(l7 );
  55. m4(l9);
  56. }
  57. }
Which of the following declarations are valid?
ArrayList<String> l1 = new ArrayList<String>();//valid
ArrayList<String> l2= new ArrayList<>();//valid
ArrayList<?> l3 = new ArrayList<Integer>();//valid
ArrayList<?> l4 = new ArrayList<String>();//valid
ArrayList<? extends Number> l5 = new ArrayList<Integer>();//valid
ArrayList<? extends Number> l6 = new ArrayList<String>();// invalid
ArrayList<?> l7 = new ArrayList<? extends Number>();// invalid
ArrayList<?> l8 = new ArrayList<?>();// invalid

We can declare the type parameter either at the class level or method level.
Declaring type parameter at class level:
class Test<T>{
//we can use T anywhere
}

Declaring type parameter at the method level
We must declare just before return type.
Example
public <T> void methodOne(T t){};//valid
public <T extends Number> void methodOne(T t){};//valid
public <T extends Number & Runnable> void method(T t){};//valid
public <T extends Number & Runnable & Comparable> void methodOne(T t){}//valid
public <T extends Number & Thread> void methodOne(T t){}//invalid
public <T extends Runnable & Thread> void methodOne(T t){}//invalid

Conclusion
The generic concept is applicable only at compile time.Hence the following declarations are equal
ArrayList<String> list = new ArrayList<>();
ArrayList<Integer> list1 = new ArrayList<I>();

You will get compiler Error, because method methodOne have same erasure type.
public void methodOne(ArrayList<String> l){}
public void methodOne(ArrayList<Integer> l){}

How compiler works w.r.t Generic code.
  • The compiler first checks type and value is added the same type or not. If not give you Compilation Error.
  • If the type and added values are of the same type, then it erasure the type and again validate the source file, if found any violation then give you Compilation Error.
Limitation of Generic Type
1.You cannot instantiate a generic type using a new operator. For example,
T  t = new T(); //compiler error

2.You cannot instantiate an array of a generic type. For example,
T[] t = new T[3];  //compiler error

3.You can declare instance fields of type T, but not of static fields of type T. For example,
Class A<T>{
T t;
static T t1; //compiler error
}

4.It is not possible to have generic exception classes. For example,
Class GenericException<T> extends Throwable{} // compiler error

5.You cannot instantiate a generic type with primitive types. For example,
List<int> list; // compiler error.