Hi there! In this article, we will learn about Java Generics, how to create generics class and methods, its advantages and how they can be used to improve the quality of our code.
Java Generics were introduced in JDK 5.0 with the aim of reducing bugs and adding an extra layer of abstraction over types. Generics mean parameterized types. The Java Generics allows us to create a single class, interface, and method that can be used with different types of data (objects) and this helps us to reuse our code.
Generic methods are those methods that are written with a single method declaration and can be called with arguments of different types.
Generics also provide compile-time type safety that allows programmers to catch invalid types at compile time.
"It is important to note that Generics does not work with primitive types (int, float, char, etc)."
Why Generics?
Let's say we want to create a list in Java to store Integer; we can be tempted to write:
List list = new LinkedList();
list.add(new Integer(1));
Integer i = list.iterator().next();
The compiler will complain about the last line as it doesn't know what data type is returned and it requires an explicit casting:
Integer i = (Integer) list.iterator.next();
Now, this cast can be annoying, we know that the data type in this list is an Integer. The cast is also cluttering our code. It can cause type-related runtime errors if a programmer makes a mistake with the explicit casting.
It would be much easier if programmers could express their intention of using specific types and the compiler can ensure the correctness of such type. This is the main idea behind generics.
Let's modify the code now..
List<Integer> list = new LinkedList<>();
By adding the operator <> containing the type, we narrow the specialization of this list only to the Integer type, that is, we specify the type that will be held inside the list. The compiler can then enforce the type at compile time. This can add significant robustness and makes the program easier to read.
We can create a class that can be used with any type of data. Such a class is known as Generics Class. Here's is how we can create a generics class in Java:
class Main {
public static void main(String[] args) {
// initialize generic class with Integer data
GenericsClass<Integer> intObj = new GenericsClass<>(5);
System.out.println("Generic Class returns: " + intObj.getData());
// initialize generic class with string data
GenericsClass<String> stringObj = new GenericsClass<>("Java Programming");
System.out.println("Generic Class returns: " + stringObj.getData());
}
}
// create a generics class
class GenericsClass<T> {
// variable of T type
private T data;
public GenericsClass(T data) {
this.data = data;
}
// method that return T type variable
public T getData() {
return this.data;
}
}
The Output
Generic Class returns: 5
Generic Class returns: Java Programming
Here, we have created a generic class named GenericsClass and this class can be used to work with any type of data.
class GenericsClass<T> {...}
Here, T used inside the angle bracket <> indicates the type parameter. Inside the Main class, we have created two objects of GenericsClass
intObj - Here, the type parameter T is replaced by Integer. Now, the GenericsClass works with integer data.
stringObj - Here, the type parameter T is replaced by String. Now, the GenericsClass works with string data.
We can also create a method that can be used with any type of data. Such a class is known as Generics Method.
Here's is how we can create a generics class in Java:
class Main {
public static void main(String[] args) {
// initialize the class with Integer data
DemoClass demo = new DemoClass();
// generics method working with String
demo.<String>genericsMethod("Java Programming");
// generics method working with integer
demo.<Integer>genericsMethod(25);
}
}
class DemoClass {
// creae a generics method
public <T> void genericsMethod(T data) {
System.out.println("Generics Method:");
System.out.println("Data Passed: " + data);
}
}
The Output
Generics Method:
Data Passed: Java Programming
Generics Method:
Data Passed: 25
And so, in the above code, we have created a generic method named genericsMethod.
public <T> void genericMethod(T data) {...}
We can call the generics method by placing the actual type and inside the bracket before the method name.
demo.<String>genericMethod("Java Programming");
demo.<Integer>genericMethod(25);
We can call the generics method without including the type parameter. For example,
demo.genericsMethod("Java Programming");
Bounded Types
Let's take a look at Bounded Types. The type parameter can accept any data types (except primitive types).
Bounded means “restricted“, we can restrict types that can be accepted by a method.
If we want to use generics for some specific types (such as accept data of number types or accept data of string types) only, then we can use bounded types.
In the case of bound types, we use the extends keyword.
<T extends A>
This means T can only accept data that are subtypes of A.
class GenericsClass <T extends Number> {
public void display() {
System.out.println("This is a bounded type generics class.");
}
}
class Main {
public static void main(String[] args) {
// create an object of GenericsClass
GenericsClass<String> obj = new GenericsClass<>();
}
}
In the above code, we have created a class named GenericsClass. Notice the expression GenericsClass is created with bounded type. This means GenericsClass can only work with data types that are children of Number (Integer, Double, and so on).
Advantages of Generics
It promotes Code Reuse: With the help of generics in Java, we can write code that will work with different types of data.
public <T> void genericsMethod(T data) {...}
The method above can be used to perform operations on integer data, string data, and so on.
Type Safety
It’s always better to know problems in your code at compile time rather than making your code fail at run time
Generics make errors to appear compile time than at run time.
// using Generics
GenericsClass<Integer> list = new GenericsClass<>();
Here, we know that GenericsClass is working with Integer data only.
If we try to pass data other than Integer to this class, the program will generate an error at compile time.
Individual Type Casting is not needed
// Using generics converts run time exceptions into
// compile time exception.
import java.util.*;
class Test
{
public static void main(String[] args)
{
// Creating a an ArrayList with String specified
ArrayList <String> al = new ArrayList<String> ();
al.add("Success");
al.add("Gideon");
String s1 = (String)al.get(0);
String s2 = (String)al.get(1);
}
}
If we do not use generics, then, in the above example every time we retrieve data from ArrayList, we have to typecast it. Typecasting at every retrieval operation can be quite stressful, in fact, annoying. If we already know that our list only holds string data then we need not typecast it every time.
// We don't need to typecast individual members of ArrayList
import java.util.*;
class Test
{
public static void main(String[] args)
{
// Creating a an ArrayList with String specified
ArrayList <String> al = new ArrayList<String> ();
al.add("Success");
al.add("Gideon");
// Typecasting is not needed
String s1 = al.get(0);
String s2 = al.get(1);
}
}
Conclusion
Generics is a powerful addition to the Java language as it makes the programmer's job easier and less error-prone. Generics enforce type correctness at compile time and it enables implementing generic algorithms without causing any extra overhead to our applications.
Thanks for reading! Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.
Connect with me:
Twitter: https://twitter.com/DaCodess
Instagram: https://instagram.com/realsuccess._
LinkedIn: https://www.linkedin.com/in/success-godday-2772651b2/
Top comments (0)