Generics in Java With Examples
What developed as a way of solving the problem of frequent run time exception- ClassCastException, became a great way of abstracting type from class or method. In short, the "<>" (angular brackets) that we so often use in the Collections framework is Java generics. It was introduced from JDK 5 and is most extensively used with the collections framework (List, HashMaps, etc). However, it can also be used to create a single class, method, and interface catering to different data types enabling efficient code reuse. It does not work with primitive data types and only works with objects.
Also Check, Complete JavaTutorial
What is Generics in Java
Java generics are mainly used to impose type safety in programs. Type safety is when the compiler validates the datatype of constants, variables, and methods whether it is rightly assigned or not.
For example, we cannot initialize a variable as an integer and assign a string value to it. To avoid such mismatch between them, we can restrict the usage of a specific type of object only, like Integer, Float, etc. Let us understand the need for generics in Java through some code snippets.
We can initialize a List in two ways in Java.
1. By not specifying the datatype of the List
Wait for a second, 1234 is not a string. How do we make sure we add only names of desserts, i.e. Strings? Enter generics, the other way of initializing a List.
2. By using <>(angular brackets) to specify the data type of the objects being added to the list bestDesserts
As you can see, "<String>" will make sure that we add only strings in the list and not any other datatype. If we were to follow the first method, there is a risk of run time exception when you try to iterate the list and retrieve your desserts but there is an Integer hiding in it.
Note: once we specify the datatype, we will not be able to add any other data type in the list. Doing so will throw compile-time errors.
Java Generic - Syntax and Naming Convention
Following a naming convention helps maintain uniformity in the codebase and facilitates easy readability. Generics in Java also have a naming convention for parameters.
The following are some of the extensively used naming conventions for generic parameters:
E – Element (used extensively by the Java Collections Framework, for example, ArrayList, Set, etc.)
K – Key (Used in Map)
N – Number
T – Parameter Type
V – Value (Used in Map)
Java Generics in Map
Generics in Java are used most extensively in Map interfaces to specify the type of parameter in the declaration of a Map.
Let us see an example of using generics in a HashMap and how to iterate it to retrieve values.
In the above example, we can see that the HashMap, map stores a <Key, Value> pair consisting of Integer and String respectively. Here we have used generics to specify the datatypes for key and value being integer and string respectively.
We can use the iterator object to retrieve values. If we fail to mention the datatypes for key and value, it can result in a compile-time error during iteration.
Types of Generics in Java
Java Generic Type Class
The real use of generics in its true sense can be observed in its application of creating classes. We can create a single class without specifying the data type of its object, in other words, using a generic data type we can create multiple classes with different data types. Consider the following two ways of setting and getting an item through a class:
On the left-hand side, we can see a class that is used to get and set a string object. This cannot be used to set any other type to the "thing". It makes the code quite rigid and not reusable.
Imagine if you had to create a new class for each kind of data type, it would be tedious. You would have to declare a new class for each kind of data type. On the other hand, the class on the right side is a generic class with a non-specific data type as an argument.
You can reuse it to pass any kind of object. All you have to do is specify the data type in the class object and just pass the right data:
Java Generics Interface
Generics can be used in interfaces as well. The class implementing the interface can specify the type of parameter for the method. The following code snippet shows an interface where <T1, T2> are the generic parameters to the interface.
The class DemoClass implements the interface DemoInterface by passing String and Integer corresponding to T1's and T2's datatype respectively. Similarly, any number of classes implementing the interface can use different data types like Integer, Float, Objects, etc.
As the name suggests, the type can be passed through a method or constructor. We can declare a generic method that can be called with arguments of different types. Based on the type of parameter passed to the method, the compiler will handle the method call appropriately. If we don't want the whole class to be parameterized, we can make only methods generic.
Let us see an java generics example :
Generics and Inheritance
There are some rules to be followed while making a generic type class as a subclass or superclass.
- A generic class can extend a non-generic class.
- A generic class can extend other generic classes, provided it has the same type of parameter.
- Non-Generic classes cannot extend generic classes unless the generic class has predefined parameters.
What is subtyping?
In object-oriented programming languages, A is said to be a subtype of B if A extends or implements B.
The bottom line is, subtyping is not allowed in parameterized types of generic.
Let us understand what this means with an example.
In the first kind of initialization, we can add Integers, Float, Double objects, or any sub-type of Number into the array numbers. However, we cannot initialize an array of type Numbers and assign it to an ArrayList with a subtype of Integer as a parameter.
When type arguments are different, generics of the same type are not compatible with each other. For example, as seen below, the List<HimalayanCat> is not a subtype of List<Cat>, even though HimalayanCat is a subtype of Cat. There is no relationship between these lists and List<HimalayanCat> is not assignable to List<Cat>.
Note: The important point to note here is that when the type arguments are different, inheritance ceases to exist.
These restrictions can be overcome by wildcards.
Generics with Wildcards
Have you ever wondered how to represent an unknown type? For example, you are defining a class or an interface and you don't know which type should be used. How would you handle this in Java? In addition to this, different generic types are not compatible with each other. How do we solve all those problems? Don’t fret. Enter wildcard. The "?" (question mark) is known as wildcard. We can represent an initially unknown data type using this.
There are 3 kinds of wildcards:
These kinds of wildcards are used when you are unsure about how an input data format is but you know what kind of data the user will enter. For example, you want to calculate the sum of a list but the list could be of type Double, Integer, Float and you should be able to handle all these conditions.
This is where you use upper-bound wildcards. They are used to relax the restriction of a variable type. To declare an upper-bounded wildcard, use the wildcard character (‘?’), followed by the extends keyword, followed by its upper bound.
In this example, we are accepting a parameter inside the method sum of List which has a wildcard character? extended by the type Number. This means this parameter could be List<Integer>, List<Double> and so on under the Number class.
Lower Bounded Wildcard
Imagine a situation where you want to expand the scope of a type. For example, you want to accept Integer but at the same time, you want to accept Number and Object too. How would you do this? Enter Lower bounded wildcard.
These are used to widen the scope of a type of variable. Using this we can pass the lower bound and its supertypes. The way we do this is again by using a "?" operator followed by the super keyword and then the lower bound class.
Here, we are just printing the list of elements that are passed in. But notice that we can pass Number and Integer both instead of just an Integer. However, if we pass Double it will throw an error as Double is not a superclass of Integer. When we use Lower bound you can only pass the class itself or its superclass objects.
Let’s now see a situation where your operation has nothing to do with a type. A quick example could be the example we saw earlier of printing a list. Printing a list is a simple operation irrespective of the data type. Broadly speaking, use this wildcard when the code inside the method is using the Object functionality but the code inside the method doesn’t depend on the data type of the parameter passed in. The way to do this is just putting a ?.
To see a practical example, we could just amend the previous example to use an unbounded type.
As we can see we can pass Integer, double, Number any type and it just takes it in and prints it.
Type erasure is a process followed by the compiler to enforce type safety in Java during compile time. Here, the compiler does the following thing:
- It replaces unbounded type <E> with an actual type of Object.
- It also typecasts variables if necessary and thus preserves type safety.
In the above code, T is replaced by Object during compile time, as below:
Generics and Primitive Data-types
Generics in Java cannot be used with primitive data types like int, double, float. You can use the respective wrapper classes for the data types. Wrapper classes are used to wrap a primitive value into a wrapper class object.
Ex - Integer class for int primitive type.
Advantages of Generics in Java
Code Reusability As we have seen from multiple instances above, we can use generics to create a single class, methods, interface for multiple types. This is essentially using the same piece of code to cater to multiple scenarios without code duplication.
Type Safety The very need for generics arose from trying to avoid type mismatch exceptions. Type safety is the crux of generics by abstracting the data type.
Individual Type Casting is not Needed Typecasting is not needed each time we create a new instance of a class. For example, in the below snippet we can see that the first scenario is creating a raw list without generics. In this case, we need to typecast each time we need to do operations on the list elements.
Whereas in the second scenario, we need not typecast each time thanks to generics.
Compile-time Type Checking Using generics allows us to save time by reducing debugging during run time. If there is a type mismatch between the declared variable and the type of the value passed, then the compiler throws an error even before the code is executed.
Used with Collections The most extensive use case for generics is with the many Collections available in Java.
What is not Allowed in Generics?
- You can’t have a static field of type
- You cannot create the static type of generic.
If static fields of type parameters were allowed, then the following code would be confusing:
Since the static field OS is shared by phone, pager, and pc, what is the actual type of os? It cannot be Smartphone, Pager, and TabletPC at the same time. You cannot, therefore, create static fields of type parameters.
- You cannot create an Instance of T
- You cannot create an instance of a type parameter T.
For instance, you cannot do the following:
- Generics in Java are not Compatible with Primitives in Declarations
As discussed above we cannot declare generics with primitive data types like int, float. Instead, we need to use the wrapper classes for those respective types.
- You can’t Create Generic Exception Class
A generic class cannot extend the Throwable class directly or indirectly.
- Generics in Java is a way of enforcing type safety through compile-time checks.
- You can use generics in classes, methods, and interfaces.
- Generics do not work with primitive data types. Hence wrapper class for the data type must be used. Ex- Integer, Float, Double, etc.
- Wildcard enhances the utility of generics by giving more control in handling unknown data types.
Q: What is the use of generics in Java?
A: Java generics have multiple uses like abstracting data types, facilitating code reusability, and better handling of unknown data types.
Q: Why are generics introduced in Java?
A: Generics were introduced to avoid run time exceptions caused due to typecasting errors.
Q: What is T in Generics?
A: We use type parameter <T> to create a generic class, interface, and method. The T is replaced with the actual type parameter when we use it.
Q: How does Generics work in Java?
A: Generic code ensures type safety. The compiler uses type-erasure to remove all type parameters at the compile time to reduce the overload at runtime.