Generics is the mechanism of creating a class or method that works for any type of data. Here we covered the complete tutorial along with suitable examples and sample programs, Examples.
Note: Generics work with objects (Integer, Double, String, etc) only. They cannot be used with primitive type data (int, double, etc).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class Rect<TYPE> { TYPE len,bre; Rect(TYPE l, TYPE b) { len=l; bre=b; } void show() { System.out.println(len+" "+bre); } } class Check { public static void main(String arg[]) { //Rect<int> t=new Rect<int>();// not valid Rect<Integer> r; r=new Rect<Integer>(10,20); Rect<Double> s; s=new Rect<Double>(2.5,3.2); System.out.print("Integer rect data:"); r.show(); System.out.print(" Double rect data:"); s.show(); } } |
Output:
1 2 |
Integer rect data:10 20 Double rect data:2.5 3.2 |
In the above example, class Rect is created as a Generic class. The class definition has <TYPE> specification along with Rect. It indicates that the object of Rect can be created with any specific TYPE.
If we want a Rect object that holds Integer type of length and breadth then we create the object as “new Rect<Integer>()” similarly if we want an object of Double type data, then we create it as “new Rect<Double>()”.
When we create a class with <Integer>, TYPE will be treated as Integer and when we create a class with <Double>, TYPE will be treated as Double. Instead of TYPE, we can use any other word there.
Why generics:
Generics is a mechanism of creating generic classes. It means a class that works for any type of data. To be clearer, let us take an example of a Stack. When we create a class for a Stack the class definition may go in the following way.
1 2 3 4 5 |
class Stack { int a[]; int top; } |
The above definition suggests that we are going to store a set of integer elements in the stack.
What should we do if we want a Stack (in the same program) that can hold a set of double values? We need to create another class for working with doubles like the following. The name of the double’s class cannot be Stack now as we have already a Stack.
1 2 3 4 5 |
class StackDouble { double a[]; int top; } |
If we want another Stack that holds data of a set of Student objects, then we need to create another class like the following.
1 2 3 4 5 |
class StackStudents { Student a[]; int top; } |
All the above classes have the same structure but only ‘type’ of data differs. In this kind of situations, “generic classes” are helpful.
At the academic level, we may need just one or two such classes for Stacks but in reality (practically) we may need a number of Stack objects that need to hold data of different types.
Suppose, we are in a situation where we need to work with 10 different types of data stored in a Stack. In that case, creating 10 different classes will result in problems like…
- time consumption,
- unreadability,
- memory wastage,
- difficulty in debugging,
- repeated code,
- need to remember different class names, etc.
to overcome these issues we have a concept called “Generics” in Java. In the C language, this generic mechanism is achieved with “unions” and in C++, the generic mechanism is achieved with “templates” concept.
So, the generic class definition would be like the following. This class serves well for storing any type of objects in the Stack.
1 2 3 4 5 |
class Stack<E> { E a[]; int top; } |
In the above definition <E> is used to represent any type of data. The line “class Stack<E>” says that we are creating a “class” named “Stack” that serves for “any type” of data.
Inside the block, we have used the line “E a[];” and it says that we are going to use an array and the type of data in each Stack object can differ.
The following line “int top;” says that a variable named “top” will be created with each Stack object and in each such object type of ‘top’ is “int” only.
To create a Stack object we can use the statements like the following.
- Stack<Integer> si=new Stack<Integer>();
- Stack<Double> sd=new Stack<Double>();
- Stack<Student> ss=new Student<Student>();
In each of these statements, the class name is always suffixed with a ‘type’ enclosed with angled brackets < and >. This is how we mention the type of data to be stored in the Stack.
Let us see a complete program so that we can have a better understanding of how things go with generics.
Generics example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
class Stack<E> { E a[]; int top; Stack() { a=(E[])new Object[100]; top=-1; } void push(E data) { a[++top]=data; } E pop() { return a[top--]; } boolean hasElements() { return top!=-1; } } class Check { public static void main(String arg[]) { Stack<Integer> si=new Stack<Integer>(); // creating a Stack that holds a set of Integer objects Stack<Double> sd=new Stack<Double>(); // creating a Stack that holds a set of Double objects Stack<Student> ss=new Stack<Student>(); // creating a Stack that holds a set of Student si.push(10); si.push(20); si.push(30); sd.push(1.2); sd.push(2.34); sd.push(56.789); sd.push(0.15); ss.push( new Student("himani",2) ); ss.push( new Student("vivek",3) ); ss.push( new Student("yashovardhan",7) ); ss.push( new Student("raveena",5) ); System.out.println("\nintegers..."); while(si.hasElements()) { System.out.println(si.pop()); } System.out.println("\ndoubles..."); while(sd.hasElements()) { System.out.println(sd.pop()); } System.out.println("\nstudents..."); while(ss.hasElements()) { System.out.println(ss.pop()); } } } class Student { String name; int standard; Student(String n,int s) { name=n; standard=s; } public String toString() { return name+" "+standard; } } |
Output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
integers... 30 20 10 doubles... 0.15 56.789 2.34 1.2 students... raveena 5 yashovardhan 7 vivek 3 himani 2 |
Example explanation:
In this example, we have created a generic Stack class with the first line as “class Stack<E>”
This class has 2 instance variables named “a” and “top”. “a” is to refer an array of “E” type of references and “top” is an integer that refers the recent element’s location.
Inside the constructor, we have created an array of Objects and type-casted it to E. So the array will be referred by “a” and we can store the corresponding types (Integer or String or Student or so).
If the ‘type’ supplied is Integer then we will store Integers. if the ‘type’ supplied is String then we will store Strings. Then, as usual, the ‘top’ is initialized with -1.
The push(), pop() and hasElements() methods are straightforward once we know the concept of Stack. In these methods, we have used ‘E’ where ever a specific type name is required. We can assume ‘E’ as the supplied type.
note: ‘supplied type’ is the one we mention when creating an object of the class
- in Stack<String> s; the String is the ‘supplied type’.
- in Stack<Student> s; the Student is the ‘supplied type’.
In main(), we have created 3 objects of the class Stack. ‘si’ holds a set of Integers, ‘sd’ holds a set of Strings and ‘ss’ holds a set of Students.
All the three objects are created from a single class “Stack<E>” but the type of data stored in them is different. Once the Stack objects are created, we have pushed some elements in each of them.
And then, we have popped all the elements from each of the Stack. As this is Stack, the data is printed in reverse order from that of inserted order.