The main objective of Generics is to provide type safety and to resolve type casting problems.

Case 1: Type-Safety: Arrays are Type-safe i.e., we can give the guarantee for the type of elements present int the array.
For example, if our program requirement is only String type of Objects, we can choose String array, by mistake if we are trying to add any other type of objects we will get compile time error.

String[] s = new String[1000];
s[0] = "kalyan";
s[1] = "chanukya";
//s[2] = new Integer(10); --> CE: incompatible type found :java.lang.Integer required:java.lang.String
s[2] = "hemanth";

Hence String array can contain only String type of objects. Due to this we can give the guarantee for the type of elements present in the array. Hence arrays are safe to use w.r.to the type. i.e. Arrays are typesafe.
But Collections are not type-safe i.e., we can’t give the guarantee for type of elements present inside the Collections.

For example, if our programming requirement is to hold only String type Objects and if we choose ArrayList, by mistake if we are trying 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("kalyan");
l.add("chanukya");
l.add(new Integer(10));
String name1 = (String)l.get(0);
String name2 = (String)l.get(1);
String name3 = (String)l.get(2);
RE: ClassCastException

Hence, we can’t give the guarantee for the type of elements present inside the Collection. Due to this Collections are not safe to use w.r.to the type i.e., Collections are not Time-safe.


Case 2: Type-Casting:
In the case of Arrays at the time of retrieval is not required to perform type casting because there is guarantee for the type of elements present inside array.

String[] s = new String[1000];
s[0] = "kalyan";
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 in the Collections.

ArrayList l = new ArrayList();
l.add("kalyan");
String name1 = l.get(0); // CE: incompatible type found :java.lang.Object required:java.lang.String
String name1 = (String)l.get(0); //Type casting is mandatory

Hence Typecasting is mandatory in Collection.
To overcome above problems of Collection SUN people introduced Generics concept in 1.5V.
Hence the main objectives of Generics are
1. To provide Time-Safety.
2. To resolve Type-Casting problems
For example, to hold only String type objects we can create Generic version of ArrayList objects as follows


ArrayList<Stringl = new ArrayList<String>();

For this ArrayList we can add only String type objects. By mistake if we are trying to add any other type then we will get Compile time error

l.add("kalyan");
l.add("chanukya");
//l.add(new Integer(10)); --> CE
l.add("hemanth");

Hence through Generics we are getting type safety
At the time of retrieval, we are not required to perform type-casting

ArrayList<Stringl = new ArrayList<String>();
l.add("kalyan");
String name1 = l.get(0); //Type Casting not required.

Hence through Generics we can solve type-casting problem.
Difference between ArrayList l = new ArrayList(); and ArrayList l = new ArrayList();

ArrayList l = new ArrayList(); ArrayList l = new ArrayList();
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 object and hence it is Type-safe
At the time of retrieval compulsory, we have to perform type-casting. At the time of retrieval, we are not required to perform type-casting.

Conclusion 1: 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 --> Base type 
<String> --> Parameter type


ArrayList<Stringl = new ArrayList<String>(); //Valid
List<Stringl = new ArrayList<String>(); //Valid
Collection<Stringl = new ArrayList<String>(); //Valid
ArrayList<Objectl = new ArrayList<String>(); //Invalid CE: Incompatible type found: ArrayList<String> required: ArrayList<Object>

Conclusion 2: For the type parameter we can provide any class or interface name but not primitive. If we are try to primitive then we will get compile time error.

ArrayList<intl = new ArrayList<int>(); //Invalid CE: Incompatible type found: int required: reference

Generic classes:

Until 1.4 Version a non-generic version of ArrayList class is declared as follows.

class ArrayList{
    add(Object o)
    Object get(int index)
}

The argument2 add method is Object and hence we can add any type of Object to the ArrayList. Due tothis 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>{
    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 objects can be creates as follows.

ArrayList<Stringl = new ArrayList();

For this requirement compiler consider version of ArrayList class is as follows.

class ArrayList<String>{
    add(String s)
    String get(int index)
}

The argument 2 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, we will get compile time error.

l.add("kalyan"); //Validl.add(new Integer(10)); //CE: Cannot find symbol symbol: method add(java.lang.Integer) location: class ArrayList<String>

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); //Type-casting is not required.

In Generics we are associating a type parameter to the class such type of parameter raised 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<Golda1 = new Account<Gold>();
Account<Silvera2 = new Account<Silver>();
//Demo Program
class Gen<T>{
    T ob;
    Gen(T ob){
        this.ob = ob;
    }
    public void show(){
        System.out.println("The type of ob is " + ob.getClass().getName());
    }
    public T getOb(){
        return ob;
    }
}
class GenericsDemo{
    public static void main(String[] args){
        Gen<Stringg1 = new Gen<String>("kalyan");
        g1.show();
        System.out.println(g1.getOb());
        Gen<Integerg2 = new Gen<Integer>(333);
        g2.show();
        System.out.println(g2.getOb());
        Gen<Doubleg3 = new Gen<Double>(10.5);
        g3.show();
        System.out.println(g3.getOb());
    }
}

Output:
The type of ob is java.lang.String
kalyan
The type of ob is java.lang.Integer
333
The type of ob is java.lang.Double
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<Integert1 = new Test<Integer>();
Test<Stringt2 = new Test<String>();

Syntax for Bounded Types:

class Test<T extends X>{
}

'X' can be either class or interface.
If 'x' is a class then as a type parameter we can pass either 'x' type or its child classes
If 'x' is an interface then as a type parameter we can pass either 'x' type or its implementation classes.
For Example>

class Test<T extends Number>{
}Test<Integert1 = new Test<Integer>(); //Valid
Test<Stringt2 = new Test<String>(); //CE: type parameter java.lang.String is not within its bound

For Example

class Test<T extends Runnable>{
}
Test<Runnablet1 = new Test<Runnable>(); //Valid
Test<Threadt2 = new Test<Thread>(); //Valid
Test<Integert3 = new Test<Integer>(); //CE: type parameter java.lang.Integer is not within its bound.

we can define bounded types even in combination also.

class Test<T extends Number & Runnable>{
}

As type parameter we can take anything which should be child class of Number and should implements Runnable interface.

class Test<T extends Runnable & Comparable>{
    //Valid
}
class Test<T extends Number & Runnable & Comparable>{
    //Valid
}
class Test<T extends Runnable & Number>{
    //Invalid because we have to take class first followed by interface.
}
class Test<T extends Number & Thread>{
    //Invalid we can't extends more than one class simultaneously
}

Note:
1. We can define bounded types only by using extends keyword and we can’t use implements and super keywords but we can replace implements keyword with extends keyword.

class Test<T extends Number>{
    //Valid
}
class Test<T implements Runnable>{
    //Invalid
}
class Test<T extends Runnable>{
    //Valid
}
class Test<T super String>{
    //Invalid
}

2. As the type parameter 'T" we can take any java identifier but it is convention ‘T’

class Test<T>{
    //Valid
}
class Test<x>{
    //Valid 
}
class Test<A>{
    //Valid 
}
class Test<kalyan>{
    //Valid 
}

3. Based on our requirement we can declare any no. of type parameters and all these type parameters should be separated with ","(comma).

class Test<A , B>{
    //Valid
}
class Test<XYZ>{
    //Valid
}
class HashMap<KV>{
    //Valid
}
HashMap<IntegerStringhm = new HashMap<IntegerString>();

Generic methods and wild-card character(?):

1. m1(ArrayList<String> l)

we can call this method by passing ArrayList of only String type.
But within the method we can add only String type of Objects to the List. By mistake if we are trying to add any other type, we will get compile time error.

m1(ArrayList<String> l){
    l.add("A");
    l.add(null);
    l.add(10); //Compile time error
}

2. m1(ArrayList<?> l)

we can call this method by passing ArrayList of any type.
But within the method we can’t add anything to the List except null because we don’t know the type exactly.
null is allowed because it is valid value for any type.

m1(ArrayList<?> l){
    l.add(10.5); //Invalid
    l.add("A"); //Invalid
    l.add(10); //Invalid
    l.add(null); //Valid
}

This type of methods best suitable for read only operation.


3. m1(ArrayList<? extends X> l)

X can be either class or interface.
If X is the class, then we can call this method by passing ArrayList of either X type or its child classes
If X is the interface, then we can call this method by passing ArrayList of either X type or its implementation classes
But within the method we can’t add anything to the List except null because we don’t know the type exactly.
This type of method also best suitable for read only operation.


4. m1(ArrayList<? super X> l)

X can be either class or interface.
If X is the class, then we can call this method by passing ArrayList of either X type or its super classes
If X is the interface, then we can call this method by passing ArrayList of either X type or super class of implementation class of X.
But within the method we can add X type Objects and null to the List.

ArrayList<Stringl = new ArrayList<String>();
ArrayList<?l = new ArrayList<String>();
ArrayList<?l = new ArrayList<Integer>();
ArrayList<? extends Numberl = new ArrayList<Integer>();
ArrayList<? extends Numberl = new ArrayList<String>(); //CE: incompatible type found:ArrayList<String> required: ArrayList<? extends Number>
ArrayList<Stringl = new ArrayList<String>();
ArrayList<? extends Stringl = new ArrayList<Object>();
ArrayList<?l = new ArrayList<?>(); //CE: Unexpected type found:? required: class or interface without bounds
ArrayList<?> l = ne w ArrayList <? extends Number>(); //CE: Unexpected type found:? extends Number required: class or interface without bounds

We can declare type parameter either at class level or method level.


Declaring type-parameter at class level:

class Test<T>{
    we can use 'T' within this class based on our requirement
}

Declaring type-parameter at method level:
we have to declare type-parameter just before return type.

class Test{
    public <T>void m1(T ob){
        we can use 'T' anywhere with in this method based on our requirement.
    }
}

we can define bounded types even at method levels also

public <T>void m1() //Valid
public <T extends Number>void m1() //Valid
public <T extends Runnable>void m1() //Valid
public <T extends Number & Runnable>void m1() //Valid
public <T extends Comparable & Runnable>void m1() //Valid
public <T extends Number & Comparable & Runnable>void m1() //Valid
public <T extends Runnable & Number>void m1() //Invalid-> first we have to take class and then interface
public <T extends Number & Thread>void m1() //Invalid -> we can't extends more than one class

Communication with non-generic code:
If we send generic object to non-generic area then it starts behaving like non generic object. similarly, if we send non-generic object to generic area then it starts behaving like generic object. i.e., the location in which the object present based on that behaviour will be defined.

import java.util.ArrayList;
class Test{
    public static void main(String[] args){
        //Generic
        ArrayList<Stringl = new ArrayList<String>();
        l.add("kalyan");
        l.add("chanukya");
        //l.add(10); //Compile time error
        m1(l);
        System.out.println(l); //[kalyan, chanukya, 10, 10.5, true]
        //l.add(10.5); // Compile time error
    }
    //Non-Generic
    public static void m1(ArrayList l){
        l.add(10);
        l.add(10.5);
        l.add(true);
    }
}

Output:
[kalyan, chanukya, 10, 10.5, true]

The main purpose of Generics is to prob=vide type safety and to resolve type casting problems.
Type safety and type casting both are applicable at compile time hence Generics concept also applicable at only compile time but not at runtime.
At the time of compilation as last step Generic syntax will be removed and hence for JVM Generic syntax won’t be available


import java.util.*;
class Test{
    public static void main(String[] args){
        ArrayList l = new ArrayList<String>();
        l.add(10);
        l.add(10.5);
        l.add(true);
        System.out.println(l); //[10, 10.5, true]
    }
}

Output:
[10, 10.5, true]
Hence the following declarations are equal.

ArrayList l = new ArrayList<String>();
ArrayList l = new ArrayList<Integer>();
ArrayList l = new ArrayList<Double>();
ArrayList l = new ArrayList();

The following declarations are equal

ArrayList<Stringl = new ArrayList<String>();
ArrayList<Stringl = new ArrayList();

For these ArrayList objects we can add only String type objects.

class Test{
    public void m1(ArrayList<Stringl){
        ====> m1(ArrayList l)
    }
    public void m1(ArrayList<Integerl){
        ====> m1(ArrayList l)
    }
}

CE: name clash: both methods having same erasure

At compile time:
1. Compile code normally by considering generic syntax.
2. Remove generic syntax.
3. Compile once again resultant code.