Generics in Java was never this EASY! — A Complete Beginner’s Guide!
Tags: java, oops, programming, generics
Published: 2023-09-11 09:07:49

Imagine you have a collection of jars, all identical in shape and size, but each one with a unique label. Now, these jars can be used to store a variety of items, from candies to spices, without any confusion. The label on each jar is the key — it tells you exactly what should go inside. Just like these labeled jars simplify your storage needs, Generics in Java are like labels for your code, making it easy to work with multiple data types while keeping things organized.

![Jars](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/apidnbroli4a8cn8jj1h.jpeg)

## What Are Generics?
Generics in Java are like those **labels** on your jars, but for your code. They allow you to create classes, methods, and interfaces that can work with different data types while ensuring type safety. These labels (or `type parameters`) specify what kind of data each container can hold, just like the labels on your jars specify what goes inside.

### The Need for Generics
Imagine if your jars had no labels. You’d have to open each one to figure out what’s inside. It would be a chaotic mess! Without Generics, your code can end up in a similar mess, with lots of redundancy. You might need to create separate classes for each data type, which is inefficient and prone to errors. Generics come to the rescue, allowing you to create a single, versatile container that can hold various types of data, just like those labeled jars help keep your kitchen organized.

## Type Parameters and Representation
In Java, generics use type parameters, which are placeholders for specific data types. These type parameters can be represented in two common ways:

1. **Single Letter Convention:** Typically, single uppercase letters are used to distinguish from regular class or interface names. The most commonly used parameters are
`E` - Element (used extensively by the Java Collections Framework)
`K` - Key
`N` - Number
`T` - Type
`V` - Value
They don't carry any special meaning; they're just placeholders.
For example, the class `java.util.HashMap<K, V>` has two type parameters, `K` and `V`, representing the type of the keys and values, respectively, stored in the map. The interface `java.util.List<E>` has a single type parameter `E` representing the type of the elements stored in the list.

2. **Descriptive Names:** Sometimes, more descriptive names like `KeyType` or `ValueType` are used for type parameters, especially when clarity is important, just as you might use labels with descriptive names for your jars.

## Generic Classes
Now, let’s delve into generic classes with code and explanations.
_Syntax of a Generic Class -_

```
class ClassName<T1, T2, ..., Tn> {
    // block of code
}
```
Here, T1, T2, …, Tn are the comma-separated type variables.
Let’s understand with an example:

```
class Box<T> {
    private T contents;

    public Box(T contents) {
        this.contents = contents;
    }

    public void printDataType() {
        System.out.println("Type: " + this.contents.getClass().getSimpleName());
    }
}

class Main{
   public static void main(String[] args) {
    Box<Integer> integerBox = new Box<>(42);
  integerBox.printDataType();

    Box<String> stringBox = new Box<>("Hello, Generics!");
  stringBox.printDataType();
}
}

// Output:
// Type: Integer
// Type: String
```
In this code, we've defined a generic class **`Box<T>`** that can hold any type of object, where **`T`** is our type parameter. The **`printDataType`** method prints the type of the contents stored in the **`Box`** using **`getClass().getSimpleName()`**.

In the **`main`** method, we create instances of **`Box`** for both **`Integer`** and **`String`** types and call the **`printDataType`** method on each of them. This should print the respective types of the contents stored in the **`Box`**.

### Analogy -

> Just like your labeled jars can hold different items, the **`Box`** class can hold different data types. The type parameter **`T`** acts like the label, ensuring that you only put compatible items inside.
>

## Generic Methods
Now, let’s delve into generic methods within a generic class.
_Syntax of a Generic Method:_

```
<T1, T2, ..., Tn> returnType methodName(parameters) {
    // block of code
}

// Example Syntax -

  //Example 1
  public <T> void methodName(T obj) {
      // block of code
  }
  
  //Example 2
  public static <T, N> T methodName(T obj, N num) {
      // block of code
  }
```
Let’s understand with an example now:

```
class Box<T> {
    private T contents;

    public Box(T contents) {
        this.contents = contents;
    }

    public T getContents(){ //method returns T type
      return this.contents;
    }
}

class Main{
   public static void main(String[] args) {
    Box<Integer> integerBox = new Box<>(42);
  System.out.println("Method Returned:" + integerBox.getContents());

    Box<String> stringBox = new Box<>("Hello, Generics!");
  System.out.println("Method Returned:" + stringBox.getContents());
}
}

// Output -
// Method Returned:42
// Method Returned:Hello, Generics!
```
In this code, we have a generic class **`Box<T>`** with a constructor that accepts an object of type **`T`** and a **`getContents`** method that returns an object of type **`T`**.

The `integerBox` object is created with an **Integer** type parameter and the `stringBox` object is created with a **String** type parameter.

In the `main()` method, we are calling the `getContents()` method on both objects and displayed the returned values on the console.

## Generic Interfaces
Similar to creating Generic Classes and Generic Methods, we can also create interfaces in Java that can be used with any type of data by using generics. This type of method is known as a Generic Interface.
_Syntax of a Generic Interface:_

```
interface InterfaceName<T1, T2, ..., Tn> {
    // block of code
}

class ClassName<T1, T2, ..., Tn> implements InterfaceName<T1, T2, ..., Tn> {
    // block of code
}



// Example Syntax -

interface Pair<K, V> {
    K getKey();
    V getValue();
}
```
`Pair` is a generic interface defining two type parameters, `K` and `V`. 
Implementing classes specify the actual types:

```
interface Pair<K, V> {
    K getKey();
    V getValue();
}


class OrderedPair<K, V> implements Pair<K, V> {
    private K key;
    private V value;

    public OrderedPair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }
}

class Main{
  public static void main(String[] args){
    OrderedPair<String, Integer> obj = new OrderedPair<>("Mobile No", 999999999);
    System.out.println("Key: " + obj.getKey());
    System.out.println("Value: " + obj.getValue());
  }
}

// Output -
// Key: Mobile No
// Value: 999999999
```
In this code, we have defined a generic interface **`Pair<K, V>`** with two type parameters, **`K`** and **`V`**, which define methods **`getKey`** and **`getValue`**. Then, we have implemented this interface with the **`OrderedPair<K, V>`** class, where **`K`** and **`V`** are specific types (in this case, **`String`** and **`Integer`**).

In the **`main`** method, we create an instance of **`OrderedPair<String, Integer>`** with values `"Mobile No"` and `999999999`.

## Bounded Types

Now, imagine if you wanted to limit the types that can be used with generics, just like specifying that a jar is for storing only chocolates. This is where bounded types come in.

![Chocolate jar](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8mq75lk67zre1zb2pzmj.jpeg)
As we saw above of how we can create generic classes, interfaces, and methods that can work with any data type, however, sometimes we may want to limit the data types that can be used with a generic to a specific set of types. In these cases, we can use bounded types by specifying the upper bound type parameter with the extends keyword.

_Syntax of a Bounded Types:_

```
<T extends A>
```
Here, the type parameter `T` can only accept data types that are of type `A` or any classes or interfaces that extends `A`.
Let’s understand with an example:

```
class NumberContainer<T extends Number> {
    private T value;

    public NumberContainer(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

class Main{
  public static void main(String[] args) {
    NumberContainer<Integer> intContainer = new NumberContainer<>(42);
    Integer intValue = intContainer.getValue(); // Works fine
    System.out.println(intValue);

    NumberContainer<Double> doubleContainer = new NumberContainer<>(3.14);
    Double doubleValue = doubleContainer.getValue(); // Works fine
    System.out.println(doubleValue);
    }
}

// Output -
// 42
// 3.14
```
In this example, **`NumberContainer`** is a generic class that can hold any type that extends the **`Number`** class, which includes numeric types such as **`Integer`**, **`Double`**, and **`Float`**.

We create **`NumberContainer`** instances for **`Integer`** and **`Double`** types and successfully store and retrieve numeric values from them.

### Failing Scenario in Bounded Types
Now, let’s see a failing scenario for the above example:

```
public static void main(String[] args) {
    NumberContainer<String> stringContainer = new NumberContainer<>("Hello"); // Compilation error!
}
```
In this scenario, we attempt to create a **`NumberContainer`** for the **`String`** type, which is not a numeric type. This results in a compilation error because it violates the constraint that the type must extend **`Number`**.

So, in this example, bounded types ensure that the container only works with numeric types and prevents non-numeric types from being stored in it.

## Conclusion

In summary, generics in Java are like labeled jars, making it easy to understand, organize, and work with multiple data types in your code. Whether you're working with classes, methods, or interfaces, generics allow you to create more versatile and reliable code. So, just like those labeled jars in your kitchen, generics help you keep your Java code neatly organized and easy to grasp.

### Closing Thoughts:
Thank you for reading our blog. We appreciate your time and interest in our content. If you have any questions, feedback, or topics you’d like us to cover in our future articles, please feel free to leave a comment below. Your input is valuable, and it helps us create content that matters to you.

Stay connected with us for more insightful articles on various topics related to technology, programming, and much more.

Happy coding!