Java Generics explained with simple definition and examples


The topic of Generics in Java is not an easy concept to understand. Unfortunately, it is the most neglected topic by students and software engineers who have just started their carrier.

This article tries to define it in a simple way with the analogy of fruits and vegetables.

Generics in Java

Generics Dictionary Definition

    "a word used to describe a general category of things or concepts"

    Let's say for example "fruit" is a generic term that encompasses many specific types of fruit, such as apples, oranges, and bananas.

    Now if someone tries to add a vegetable to this list, say "potato", this does not remain a genetic type, rather the definition of genetics does not hold true, or we must say it is an error right?

    Potatoes the odd here

    Continuing to the analogy above, in Java, Generics is a way of creating a general category of classes or methods that can work with different types of data in a type-safe manner.

    Just like how "fruit" is a general category that can include different types of fruits, such as apples, oranges, and bananas, Generics in Java can create a class or method that can work with different types of data, such as Integer, String, or custom data types.

How is Genetics helpful?

    It is always better to identify bugs at compile time to avoid runtime exceptions. With the help of Generics we can achieve this to a greater extent.

    There are two important concepts of generics that help avoid runtime exceptions.

    1. Type Safety: By using generic type parameters, the usage of the class is limited to the specified types, thus avoiding the possibility of a ClassCastException runtime exception.

    2. Elimiate Type Casting: When a class defined as type-safe is instantiated with a type-parameter, no casting is required.


Explained With Examples:

Say we have a parent class Fruit and its sub-classes as Apple, Orange, and Banana.

public class Fruit {
    private String name;
    private String color;

    public Fruit(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public String getColor() {
        return color;
    }
}
public class Apple extends Fruit {
    public Apple(String color) {
        super("Apple", color);
    }
}
public class Orange extends Fruit {
    public Orange(String color) {
        super("Orange", color);
    }
}
public class Banana extends Fruit {
    public Banana(String color) {
        super("Banana", color);
    }
}

We also have a class Potato which is a sub-class of Vegetable.

public class Vegetable {
    private String name;
    private String color;

    public Vegetable(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public String getColor() {
        return color;
    }
}
public class Potato extends Vegetable {
    public Potato(String color) {
        super("Potato", color);
    }
}

Now let's create a generic type that consists of a list of fruits:

import java.util.ArrayList;
import java.util.List;

public class FruitList<T extends Fruit> {
    private List<T> fruits;

    public FruitList() {
        fruits = new ArrayList<>();
    }

    public void addFruit(T fruit) {
        fruits.add(fruit);
    }

    public List<T> getFruits() {
        return fruits;
    }
}

Now, if you try to create a list of fruits with potatoes, you will get a compilation error.

FruitList<Apple> appleFruitList = new FruitList<>();
FruitList<Banana> bananaFruitList = new FruitList<>();
FruitList<Orange> orangeFruitList = new FruitList<>();

FruitList<Potato> potatoFruitList = new FruitList<>(); //Compliation Error

You can clearly see, generics make the program type safe and bring up issues during compile time to avoid runtime issues.


Example with Java Collection API

//Example 1
LinkedList listWithoutGenerics = new LinkedList();
listWithoutGenerics.add("A");
listWithoutGenerics.add(1);
String s1 = (String) listWithoutGenerics.get(1); //Need Cast Expression with String
String s2 = (String) listWithoutGenerics.get(2); //Need Cast Expression with String

//Example 2
LinkedList<String> listWithGenerics = new LinkedList();
listWithGenerics.add("A");
listWithGenerics.add(1); //Compilation Error - Required String - Provided int
String s3 = listWithGenerics.get(1); //ClassCastException

In example 1, we created a LinkedList without the use of Generics, though we were able to add elements without a problem, we need to cast the type when trying to fetch elements from the list, and it's not a safe thing to do as we see for string s2 where we get a ClassCastException as an Integer is required and not a String type.

In example 2, we made use of generics, hence if you try to add any other type of element to the list we get a Compilation Exception, this adds type-safety and avoids the need for casting.

Facing issues? Have Questions? Post them here! I am happy to answer!

Author Info:

Rakesh (He/Him) has over 14+ years of experience in Web and Application development. He is the author of insightful How-To articles for Code2care.

Follow him on: X

You can also reach out to him via e-mail: rakesh@code2care.org

Copyright © Code2care 2024 | Privacy Policy | About Us | Contact Us | Sitemap