What is Generics in Java?
By: Michael Tong
Tags: Java, Core Java, Java8

Photo by RetroSupply on Unsplash

As we know, java is a strongly typed class. It expects every single object instance or class it runs into to be specified a type.

And that’s fine if your application works as a single standalone application that does not interact with any third part source codes. Reality is, it doesn’t really work that way and most likely you might be interacting with other external sources.

Let’s say we have the following code:

We are assuming this function will return the string in all scenarios. Totally fine but if we are working with third party code, this function can be returning something different.

Enters generic.

Generic allow users to parameterize classes, methods or interfaces to support one or more types. This can be one of any class type, any child class of a specified generic type, or a parent class of a specified generic type.

Here is a basic example of a generic class:

What makes it a generic class? First of all, it supports a wildcard which means GenericsClass can be instantiated with any object type. For example:

See how generics give us the option to instantiate the instances on different object types? Generally, if a class is strongly typed and there was an issue with the type of the instance that was instantiated, a runtime error will occur. With generics, if there are any issues with the type parameters, it will be captured in compile time which is easier to detect and understand what the problem is.

Well…what is a generic method then?

Let’s take a look at the following example:

Imagine we want to build a functionality to print an array without generics. Because Java is a strongly typed language, we would literally have to build a printArray function for every type of object that we want to print the value for.

Ouch! That sounds horrible from maintenance point of view is it? What if we want to print values from the array for class 1, class 2, class 3, …class n? We will have to replicate the same method just because there are different classes.

That’s the magical part about generics. It doesn’t care about the type and it focuses on the functionality of printing out the value.

How about a generic interface? how does that look like?

Over we can implement the interface with any generic type we can support, such as a String or an Integer. And with each class that supports the generic type, it gives us further flexibility as to how we design the classes to support the generic type, with each class implementing their set of characteristics with the interface methods.

So far we have talked about generic wildcards, also known as unbounded wildcards. What if we want to define boundary for a list of classes our generic class or method want to support?

In reality, we have two type of bounded wildcards:

Upper bound wildcards (For example, <? extends List>)

Lower bound wildcards(For example, <? supers ArrayList>)

Upper bound wildcards sets a boundary, defining the list of child classes the generic allows while lower bound wildcards set a boundary for what parent/grandparent classes the generic supports.

Let’s take a look at the following example:

Over here, for arr and arrTwo, any subclasses of Number(such as Long) and the Number class itself is supported via the upper bound wildcard.

for arrThree, if you try to support an object class that is not a child class of Number, a compile time error will result, similar to what you will see with the following message:

Error java: incompatible types: java.util.ArrayList<Session_1.Department> cannot be converted to java.util.List<? extends java.lang.Number>

For arrFour, a list of type of number or a type of Object List, a parent class of Object, can be used to instantiate it. If we try to instantiate arrFour with a class that is not a parent class of Number, it will result into a compile type error.

Here are a few more things to remember about generics:

type inference: java’s compiler has the ability to look at each method invocation and corresponding declaration to determine the type argument. For example:

type erasure: refers to java’s compiler to enforce type constraints at compile time only and discard generic element types at runtime. Let’s say we have the following:

If we declare a GenericCard that is of type string, it will change the declaration for the GenericCard class in the following way:

That’s it! That pretty much covers everything with generic in java.