Generics concept in Java
Generics concept has made a revolutionary enhancement to the Core Java language. The concept allows Java programmers to pass types as arguments to classes just like values are passed to methods. It ensures type safety of objects and provides compile time checking. In fact, there is no need to typecast the object.
The term “generics” means parameterized types. These types are important because they enable programmers to create classes, interfaces, and methods in which the type of data upon which they operate is specified as a parameter. With generics concept, it is easier for us to create a single generic class, for example, that automatically works with different types of data. Similarly, we create interfaces or methods that operates on type parameters and can be called generic interfaces or generic methods.
Creating a Simple Generic class
The signature of a generic class is given below.
class class-name<type-parameter-list> {
//code goes here
}
We begin with a simple example of a generic class. The following program defines two classes. The first one is the generic class Generic, and the second class is GenricsDemo, which uses the first one. Here, ‘T‘ is a type parameter that will be replaced by a reference type (not primitive type) when an object of class type Generic is created. Notice that ‘T‘ is contained within angle brackets “< >“. As because class Generic uses a type parameter, it is a generic class, which is also called a parameterized type. See the code below.
GenricsDemo.java
class Generic<T> { T obj; // Declare an object reference of type T // Constructor public Generic(T obj) { this.obj = obj; } // Instance method returning an object of type T public T getObject() { return obj; } // Instance method printing type of T public void printType() { System.out.print("Type = " + obj.getClass().getName()); } } public class GenericsDemo { public static void main(String[] args) { // Create a Generic object for String types Generic<String> strObj = new Generic<String>("Asia"); strObj.printType(); String val1 = strObj.getObject(); System.out.print("; Value = " + val1 + "\n"); // Create a Generic object for Double types Generic<Double> doubleObj = new Generic<Double>(123.75); doubleObj.printType(); double val2 = doubleObj.getObject(); System.out.print("; Value = " + val2 + "\n"); // Create a Generic object for Integer types Generic<Integer> intObj = new Generic<Integer>(99); intObj.printType(); int val3 = intObj.getObject(); System.out.print("; Value = " + val3 + "\n"); } }
Output:
Type = java.lang.String; Value = Asia
Type = java.lang.Double; Value = 123.75
Type = java.lang.Integer; Value = 99
Generics and Type Safety
Generics are largely intended for use with the Java Collections Framework. To demonstrate this we use the following before-and-after example of the way elements in an ArrayList are accessed:
String item = (String)list.get(1); // without generics String item = list.get(1); // with generics
In the first example, we had to perform the usual type cast, because the current collection classes work with Objects. It’s up to the programmer to determine the type and do the appropriate type conversion.
In the second example, there is no type cast. So how did the get() method know about returning a String object? The simple answer is that when the ArrayList was instantiated, it was specifically told to work with Strings.
Let’s have a look at how this was done:
ArrayList list = new ArrayList();
We will see that the two sets of angle brackets contain the ‘type’ we pass to the class as we instantiate it. The given list will now work with String types only. In essence, it will not compile if any attempts are made to add objects of the wrong type. In this way, generics concept brings type safety of objects and provides compile type checking without making any type cast.
Let’s have a look at the sample ArrayList implementation below to get an idea of what’s going on:
public class ArrayList {
public boolean add(Element o) {
// code excluded
}
public Element get() {
// code excluded
}
}
The key idea to all this is the declaration. This rather odd looking syntax really just serves the same purpose parentheses serve for method parameters, except, of course, that we are passing types along, not values. By the way, the real ArrayList class uses as the type parameter – there is nothing special about, just as there is nothing special about any method parameter names we choose.
See the complete code below for understanding the concept:
TestGenerics.java
import java.util.ArrayList;
public class TestGenerics {
public static void main(String[] args) {
//Without using Generics
ArrayList list1 = new ArrayList();
list1.add("Ram");
list1.add("Delhi");
list1.add(9999.95);
list1.add(40); //compiles successfully
//Selecting an element of list1
String item1 = (String)list1.get(1); //explicit type casting is required
System.out.println("Element of list1 is: " + item1);
//Using Generics
ArrayList<String> list2 = new ArrayList<String>();
list2.add("Book");
list2.add("Pen");
list2.add("Diary");
//list2.add(40); //causes compile time error because 40 is not a String
//Selecting an element of list2
String item2 = list2.get(1); //explicit type casting is not required
System.out.println("Element of list2 is: " + item2);
}
}
Output:
Element of list1 is: Delhi
Element of list2 is: Pen
A Generic Method Example
It is possible to declare a generic method that uses one or more type parameters of its own. The signature of a generic method is given below.
<type-parameter-list> return-type method-name(parameter-list) {
//code goes here
}
Here, we create a generic method that is enclosed within a non-generic class named GenericMethodDemo. The method call is placed inside a for loop. When called, it adds one element to the ArrayList object and at the same time returns that element from it to main(). Finally, the program will print the ArrayList object. See the program below.
GenericMethodDemo.java
import java.util.ArrayList;
public class GenericMethodDemo {
static <T> T addAndReturn(T e, ArrayList<T> al) {
al.add(e);
return e;
}
public static void main(String[] args) {
ArrayList list = new ArrayList();
for (int i = 10; i <= 60; i = i + 10) {
int item = addAndReturn(i, list);
System.out.println("item : " + item);
}
System.out.println("List = " + list);
}
}
Output:
item : 10
item : 20
item : 30
item : 40
item : 50
item : 60
List = [10, 20, 30, 40, 50, 60]
Generic Type Naming Convention
Generic type naming convention in Java helps to understand code easily and having a naming convention is one of the best practices of Java. In fact, generics also comes with its own naming conventions. Generally, type parameter names are single, uppercase letters to make it easily distinguishable from java variables. The most commonly used type parameter names are:
- E – Element (used extensively in the Collections Framework, for example ArrayList, Vector etc.)
- K – Key (Used in Map)
- N – Number
- T – Type
- V – Value (Used in Map)
Generics Wildcards
The question mark (?) symbol is the wildcard in generics and represent an unknown type. The wildcard can be used as the type of a parameter, field, or local variable and sometimes as a return type. We can’t use wildcards while invoking a generic method or instantiating a generic class. In the following sections, we will learn about upper bounded wildcards, unbounded wildcards, and lower bounded wildcards.
1) Upper Bounded Wildcard
Upper bounded wildcards are used to loosen the restriction on the type of variable in a method. Suppose we want to write a method that will return the sum of numbers in the list, so our code snippet will be something like this.
public static double sum(List list) {
double sum = 0;
for(Number n : list){
sum += n.doubleValue();
}
return sum;
}
Now the problem with above implementation is that it won’t work with List of Integers or Doubles because we know that List<Integer> and List<Double> are not related. So, this is the scenario when an upper bounded wildcard is helpful. We can use generics wildcard with extends keyword and the upper bound class or interface that will allow us to pass argument of upper bound or it’s subclasses types.
The above implementation can be modified like the following program.
import java.util.ArrayList;
import java.util.List;
public class GenericsUpperBounded {
public static void main(String[] args) {
List list_ints = new ArrayList<>();
list_ints.add(3);
list_ints.add(5);
list_ints.add(10);
double sum = sum(list_ints);
System.out.println("Sum of ints = " + sum);
}
public static double sum(List<? extends Number> list){
double sum = 0;
for(Number n : list){
sum += n.doubleValue();
}
return sum;
}
}
Output:
Sum of ints = 18.0
It’s quite similar like writing our code in terms of interface, in the above method we can use all the methods of upper bound class Number. Note that with upper bounded list, we are not allowed to add any object to the list except null. If we will try to add an element to the list inside the sum() method, the program will not compile.
2) Unbounded Wildcard
Sometimes we have a situation where we want our generic method to be working with all types, in this case, an unbounded wildcard can be used. It is same as using <? extends Object>.
public static void showData(List<?> list){
for(Object obj : list){
System.out.print(obj + " ");
}
}
See the complete code below.
import java.util.ArrayList;
import java.util.List;
public class GenericsUnbounded {
public static void main(String[] args) {
List list_ints = new ArrayList<>();
list_ints.add(3);
list_ints.add(5);
list_ints.add(10);
showData(list_ints);
}
public static void showData(List<?> list){
for(Object obj : list){
System.out.print(obj + " ");
}
}
}
Output:
3 5 10
We can provide List<String> or List<Integer> or any other type of Object list argument to the showData() method. Similar to upper bound list, we are not allowed to add anything to the list.
3) Lower Bounded Wildcard
Let us suppose we want to perform summation of Integers to a list of integers in a method, we can keep the argument type as List<Integer> but it will be tied up with Integers whereas List<Number> and List<Object> can also hold integers, so we can use a lower bound wildcard to achieve this. We use generics wildcard (?) symbol with super keyword and lower bound class to achieve this.
We can pass lower bound or any super type of lower bound as an argument, in this case, Java compiler allows to add lower bound object types to the list. See the method named sumOfIntegers() below to understand this process:
public static void sumOfIntegers(List<? super Integer> list) {
Integer sum = 0;
for(Object t: list) {
sum = sum + (Integer)t;
}
System.out.println("Sum: " + sum);
}
See the complete code below.
import java.util.ArrayList;
import java.util.List;
public class GenericsLowerBounded {
public static void main(String[] args) {
List list_ints = new ArrayList<>();
list_ints.add(3);
list_ints.add(5);
list_ints.add(10);
sumOfIntegers(list_ints);
}
public static void sumOfIntegers(List<? super Integer> list) {
Integer sum = 0;
for(Object t: list) {
sum = sum + (Integer)t;
}
System.out.println("Sum: " + sum);
}
}
Output:
Sum: 18