Java is a type-safe language. In the sense, in Java, it is required to specify the data type of the variable or the method argument while declaring it. For instance, see the examples below:
int var = 5; // type of the variable is int void show(String arg){} // type of the argument arg is String
If we assign values to the variables other than the defined data types, Java throws a compile-time error of ‘type mismatch’. This is known as achieving type safety.
What is Generics and why is it introduced in Java?
Let’s consider a scenario where we want to write a method to sort elements in an array. In this case, we want to write a method that can sort an array consisting of Integers/Strings/Characters or any other data type elements. This is when we use Generics;
There are three implementations of Generics:
- Generic interfaces
- Generic classes and
- Generic methods
Generics is similar to the concept of Templates in C language. It helps in writing a piece of code that can be used to work with different data types. In simple words, it provides us with code re-usability. It is important to note that Generics only work with Classes and their instances but not with primitive data types.
All the classes and interfaces from Java Collections such as List
, Set
, ArrayList
, HashSet
, etc., are examples of Generic types. Let’s understand this with an example.
Creating an ArrayList
of Integers.
List<Integer> i = new ArrayList<Integer>();
Creating an ArrayList
of Strings.
List<String> s = new ArrayList<String>();
In the above examples, we are using the same class, ArrayList
, for creating an array of Integers as well as Strings. Here we have mentioned the type argument in angular brackets. This is important to achieve type safety.
What happens if we do not pre-define the Type while instantiating a generic class?
If we do not specify the Type argument while declaring the List, then it will look something like this:
List al = new ArrayList();
Here we will be able to add all kinds of values into the List as shown below:
al.add(10); // Adding Integer al.add(“Hello”); // Adding String
It will not throw any error while adding the elements; but, while retrieving them we will have to typecast the retrieved elements into wrapper class objects. This is when a runtime error occurs if we miss typecast String to Integer or vice-versa.
So, it’s always better to handle errors at compile time rather than at run time and therefore it is recommended to specify the Type argument while declaring the instance of a generic class and make it type secure.
Let’s see how to create Generic Interfaces, Classes and Methods
Generic Interfaces
Let’s take the example of the List interface. It is defined as below in java.util
package;
public interface List<E> extends Collection<E> { // -- Interface code goes here }
We use angular braces while creating Generics and the E inside the angular braces can be replaced by any specified Type. The angular brackets can have any capital letter inside it but for the purpose of readability, we either use E (Element) or T (Type).
Examples for declaring variables of the interface:
List<Integer> li; // The E in the class declaration will be replaced with <Integer>. List<Double> di; // The E in the class declaration will be replaced with <Double>.
We can add only Integer values to list li
, and double values to list di
, else a compile-time error will be thrown. Note that, we can’t instantiate objects of interfaces; rather, we can create Generic classes from the interfaces and then we can instantiate objects of generic classes.
In above example, we have used List interface; and declared the variables of it. We can create the objects of the classes which are derived from this interface; and we can assign them to the above defined variables. The code looks like below;
li = new ArrayList<Integer>(); di = new ArrayList<String>();
Observe that, ArrayList
is the generic class which is extended from the generic interface List
.
Creating Generic classes
Let’s understand this by creating our own Generic Class:
class Demo<T> { T value; // variable of Generic type // -- Demo class code goes here }
Here we can see the member variable of this class is declared as a generic type as this is going to hold the data for this class. Therefore, the class to function as a generic type, its data members should also be of a generic type.
Creating Generic methods
Let’s create getter
and setter
methods for variable value
of our above class Demo<T>
:
// getter method public T getValue() { return value; } // setter method public void setValue(T value) { this.value = value; }
As we can see, the above methods are generic methods as the return type of getter
method is a generic type and the argument for the setter
method is of a generic type.
So, now the class Demo is a complete generic class with methods and variables defined to work with the generic data.
Now let’s create an instance of the class Demo<T>
.
Demo<Integer> demo = new Demo<Integer>();
This tells us that the class object will only hold Integer
values.
demo.setValue(10); //valid
And if we attempt to set any other value type other than Integer
to the above object; compiler will throw an error. Below statement is an example of it;
demo.setValue("hello!"); // This will throw an error
error: incompatible types: String cannot be converted to Integer
This concept of specifying and telling the generic class which Type of data to accept, is called Generics.
Multiple generic type arguments
We can also create classes with multiple generic Type arguments like shown below:
class Demo<T1, T2> { T1 value1; T2 value2; }
The instance of this class is created like this:
Demo<Integer, String> d = new Demo<Integer, String>;
An example of this type of generic class is the Map
class from Java Collections.
I urge you to experiment more with the above-shown examples and try them on your own. GoodLuck!
2 thoughts on “Java – How to use Generics in Java?”