In this section, we will learn what the Generics are and how they work in Java.
What is Generic in Java?
So far, any time we’ve created a class, the data type of all the class members was specified beforehand.
For example:
public class ClassName { private String name; public void setName(String name){ this.name = name; } }
As you can see, the data type of the member variable and the parameter in this class is already declared.
But sometimes we don’t know what the data type of class member will be until at runtime when an object is being created from that class!
This is where the generic type comes in and can help us.
Via generic type, we can set an arbitrary name as the data type for parameters or member variables of a class. In this case, when we want to create an object from the class, at that moment we set the actual data type to be replaced with that arbitrary name.
Java Generic Class Example:
public class Addition<T> { private T xxx; public void setName(T xxx){ this.xxx = xxx; } public T getName(){ return this.xxx; } }
Here, the variable named `xxx` does not have a specific data type. Also, the parameter of the `setName` method as well as the return data type of the `getName()` method is an arbitrary name!
All of them share one letter and that is `T` (AKA type parameter).
For example, the `getName()` method returns a value that is of type `T`. The `setName()` method assigns a value of type `T` to the member variable that is also of type `T`.
Note: the letter `T` is called type parameter, and it’s an arbitrary name in which we could use other letters or words as well. But the point is we designed this class in a way that it can now work with multiple types of data. This is because the class is generic and when creating an object from this class, we can use any legit data type to replace the letter `T` as you’ll soon see.
How to declare a generic class? (Generic class syntax)
Alright, before we get into the details of how we can create an object from a generic class, let’s get into the details of how we turn a class into generic:
The first step is to use angle brackets `<>` after the name of a class.
In this pair of brackets, we declare the type of parameters that can be names or letters in which we want to use in the class.
In the example above, we use the letter `T`. So now the compiler knows that this letter is nothing but a type of parameter that will be replaced with an actual data type when an object from that class is created. If we don’t declare the type parameter in the angle brackets, we will get compile time error.
Note: we can use more than one type of parameter and if we do so, they should be separated from each other via comma `,`.
Example: creating objects from a generic class in Java
Let’s create another generic class and this time we use 3 type parameters:
public class Addition<T, D, F> { private T tt; private D dd; private F ff; public Addition(T tt, D dd, F ff) { this.tt = tt; this.dd = dd; this.ff = ff; } public T getTt() { return tt; } public void setTt(T tt) { this.tt = tt; } public D getDd() { return dd; } public void setDd(D dd) { this.dd = dd; } public F getFf() { return ff; } public void setFf(F ff) { this.ff = ff; } }
As you can see, first we set the type parameters (letters in this case) in the angle brackets and now the compiler knows that these 3 letters are type parameters and so we won’t get a compile time error.
OK, now that we know how we can turn a class into generic, let’s create an object and see how to replace these type parameters with actual data types:
Creating objects from Generic types:
Create a class named `Simple` with this body:
public class Simple { public static void main(String[] args) { Addition<String, Integer, Double> ad1= new Addition<String, Integer, Double>("John",30, 4.3); System.out.println(ad1.getDd()); System.out.println(ad1.getFf()); System.out.println(ad1.getTt()); } }
Output:
30 4.3 John
As you can see, after the name of a generic class, we use the angle brackets to set the actual data type of the type parameters.
How to create Objects from a Generic Class?
So considering that the `Addition` class was defined with this order of type parameters:
public class Addition<T, D, F>{//body…}
When we called the `new Addition<String, Integer, Double>`, the letter `T` was replaced with the `String` data type, the letter `D` was replaced with `Integer` data type and the letter `F` was replaced with the `Double` data type.
Basically, the compiler behind the scene and after replacing the type parameters with their data type, it creates the class below to support the object that was created:
public class Addition { private String tt; private Integer dd; private Double ff; public Addition(String tt, Integer dd, Double ff) { this.tt = tt; this.dd = dd; this.ff = ff; } public String getTt() { return tt; } public void setTt(String tt) { this.tt = tt; } public Integer getDd() { return dd; } public void setDd(Integer dd) { this.dd = dd; } public Double getFf() { return ff; } public void setFf(Double ff) { this.ff = ff; } }
Also, the data type of the variable that is going to store the reference object should also be clearly specified. This means we need to replace those type parameters with the correct data types.
Note: the order of data types that is set in the angle brackets `<>` of the variable should match the target object.
Addition<String, Integer, Double> ad1= new Addition<String, Integer, Double>("John",30, 4.3);
As you can see, both data types on the left and right side of the assignment `=` operator exactly match.
Later in this tutorial, you’ll see many Java built-in classes that use type parameters. But for now just to give you a glance of how powerful generic classes can be:
ArrayList and Generics in Java
There’s a class named `ArrayList` that acts like an array except the length of an object of type ArrayList is dynamic. This means the length grows at runtime if needed. (The size of usual arrays is static and should be defined when the array is declared).
The ArrayList class is a generic one and this means we can create one object of type `ArrayList` that can store `Integer` values, another one of type `Double` to store double values, another one of type `String` that can store values of type `String` and so on.
Java ArrayList and Generics Example:
import java.util.ArrayList; public class Simple { public static void main(String[] args) { ArrayList<String> sList= new ArrayList<>(); sList.add("One"); sList.add("Two"); sList.add("Three"); for(String s: sList){ System.out.print(s+", "); } System.out.println("\n--------------"); ArrayList<Integer> integers = new ArrayList<>(); integers.add(10); integers.add(300); integers.add(400); for (int i: integers){ System.out.print(i+", "); } } }
Output:
One, Two, Three, -------------- 10, 300, 400,
As you can see, we’ve created two objects of type `ArrayList` and the first one is used to store `String` values and the other is used to store `Integer` values.
Note: the `ArrayList` is in the `java.util` package, so make sure the package is imported as well.
In the example above, when we did create an object of type `ArrayList` we didn’t set the actual type in the angle brackets`<>` and it was empty.
In a situation like this, the compiler will automatically infer the correct type and it will replace the type parameter with the right type in the target class (`ArrayList` in this case).
Note: the use of empty angle brackets `<>` is only allowed when creating object but not for the identifier that is going to store the reference object!
Java Upper bound and Generic classes
There are times that we want to design a generic class and want to limit the data-types that can be used as the replacement of the type parameter in that class.
This is where we can use Upper Bound.
Via upper bound, when declaring a type parameter in a class, we can define that the target data type should be of a type that either implement a specific interface or extended to a specific class.
This is done via the `extend` keyword within the angle brackets `<>`.
Example: Creating upper bound generic class
public class Employee { public void jobDescription(){ System.out.println("I'm an employee."); } } public class Manager extends Employee { @Override public void jobDescription(){ System.out.println("I'm a manager. "); } }
Note: the `Manager` class here extended to the `Employee` class.
public class Generic<T extends Employee> { public void printJobDescription(T t){ t.jobDescription(); } }
Here, the `Generic` class has one type parameter (the letter T) which extends to the `Employee` class. This means when creating an object from this class, the replaced data type should be either the `Employee` class or a type that derived from the `Employee`.
Note: because the compiler knows the range of final data types for this type parameter (`T`), it allows us to use the public members of the parent data type (Employee in this case). For example, here we could call the `jobDescription ()` method of the `Employee` class without any error.
Alright, now let’s create an object from the `Generic` class and see how it works in practice:
public class Simple { public static void main(String[] args) { Generic<Employee> employeeGeneric = new Generic<>(); employeeGeneric.printJobDescription(new Employee()); Generic<Manager> managerGeneric = new Generic<>(); managerGeneric.printJobDescription(new Manager()); } }
Output:
I'm an employee. I'm a manager.
The data type of the `employeeGeneric` is `Generic<Employee>` and because the replaced data type matches the `Employee` class, it works and there’s no compile time error.
Also, the data type of the `managerGeneric` variable is `Generic<Manager>`. The `Manager` class extends to the `Employee` class, so we can use this type as the replacement of the type parameter in the `Generic` class.
Note: we left the angle brackets of the object creation empty because as mentioned in the generic class section, the data type will be automatically inferred by the compiler.
But if we used another type that didn’t derive from `Employee` class, we could get compile time error.
Java generic upper bound second example:
public class Simple { public static void main(String[] args) { Generic<String> employeeGeneric = new Generic<>(); } }
Output:
Error:(6, 17) java: type argument java.lang.String is not within bounds of type-variable T
Java Lower bound and Generic classes
Other than upper bound, we have lower bound as well that can be used with the wildcard. This is explained in details, in wildcard section.