Introduction to Generics

<aside> 💡 Generics are classes or methods that are not just limited to a specific data type but accepts a more general data type format in which data type can be specified at the time of initialization. Note: They are only compile time feature and the generic code has no significant over the byte code.

</aside>

The Need for Generics

Suppose we need to store a list of Integers in an object then we would create a class and make it hold a list of Integers in it. But if our requirement changes to store some other data type such as a custom User objects then we need to create another class to hold the list of Users.

One way to overcome this problem is to make the list hold objects of type Object. Since Object is a parent class of every reference type object. We can easily store every type of object in this list. But the problem with this implementation would be that the list would contain heterogeneous data types. So if we retrieve the data from the list we have no idea what kind of data it is holding and casting it to some other type might throw an InvalidClassCastException. In cases like this we can use Generics.

<aside> âš ī¸

Generics are not just limited to classes. We can also use them for methods.

</aside>

<aside> ❔

Can we also store ints in the List of Objects?

Yes, We can also stored integers in the list of Objects. Since int is a primitive data type, Java would wrap this int in Integer.valueOf(int) class this would make it hold an object of the Integer which is a reference type object.

</aside>

Generic Class

Below is an example of a generic class.

// Generally we use T to denote the changing object type
// T stands for Template (used generally)
public class GenericList<T> {
	 // creating an array of type Object and then casting it to T
   private T[] items = (T[]) new Object[10];
   private int count;

   private void add(T item) {
       items[count++] = item;
   }
  
   private T get(int index) {
       return items[index];
   }
}

Like methods have parameters, generic classes also have parameters. We should specify the data type T while creating an instance of the class. Example —

// Don't need to add Integer on the right side as it is inferred from left side
GenericList<Integer> list = new GenericList<>();
list.add(1);

<aside> â„šī¸

Boxing & Wrapper Class

Every primitive type in Java have a wrapper class. Using the wrapper class we can store primitive type variable inside a reference type object.

GenericList<Integer> genericList = new GenericList<>();
genericList.add(1); // Boxing
System.out.println(genericList.get(0)); // Unboxing

When a primitive type object is wrapped inside a wrapper class, it is known as boxing. When we convert an object of a wrapper type to its primitive type it is known as unboxing. If the process of boxing is implicitly done then it is called as autoboxing.

</aside>

Adding Constraints to the Template T

In the above example, we can create a GenericList object of any template data type. But, If we want to apply some constraint on the template T then we can use the following.

We need to extend T to the parent class to which we want it to be constrained. The template T can be extended to Class as well as Interfaces. For both the cases we need to extend and not implement, as we do for interfaces.

public class GenericList<T extends Number> { }

In the above snippet, the template T is bounded and restricted. Now it can only belong to classes that inherit Number class or in other words T can only belong to the Number class or the subclasses of the Number class. This is known as the bounded type parameter.