DEV Community

Cover image for Understanding Generics in Java: A Shopping Cart Example with Custom Classes
Matheus Bernardes Spilari
Matheus Bernardes Spilari

Posted on

Understanding Generics in Java: A Shopping Cart Example with Custom Classes

Generics in Java are a cornerstone of type-safe and reusable code. They allow developers to create classes, methods, and interfaces that can work with any type of data, making the code more robust and flexible. In this article, we’ll explore the concept of generics with an example of a shopping cart that can hold different types of fruits, modeled as custom classes.


Why Use Generics? The Problem with Arrays

In Java, arrays are type-specific, which means an array of one type cannot hold elements of another type:

String[] fruits = new String[3];
fruits[0] = "Apple";  // Valid
fruits[1] = 123;      // Compile-time error
Enter fullscreen mode Exit fullscreen mode

While this type enforcement is useful, arrays are limited in flexibility. For instance, if we want to create a shopping cart that can hold objects representing various fruits—like bananas, apples, and grapes—arrays would require significant manual handling.

Generics solve this problem by allowing us to define flexible yet type-safe data structures. Let’s implement this concept step-by-step, starting with creating our custom Fruit classes.


Step 1: Defining the Fruit Classes

We’ll create a base class Fruit and three specific fruit classes: Banana, Apple, and Grape. Each class will have some unique properties.

// Base class for all fruits
public abstract class Fruit {
    private String name;

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

    public String getName() {
        return name;
    }
}

// Specific fruit classes
public class Banana extends Fruit {
    public Banana() {
        super("Banana");
    }
}

public class Apple extends Fruit {
    public Apple() {
        super("Apple");
    }
}

public class Grape extends Fruit {
    public Grape() {
        super("Grape");
    }
}
Enter fullscreen mode Exit fullscreen mode

Why an abstract class ?

We chose an abstract class for Fruit to provide shared functionality (e.g., storing a name) and represent a clear hierarchical relationship. However, in cases where multiple inheritance or simpler contracts are needed, an interface could be a better choice. This topic is broad enough to merit its own exploration!


Step 2: Implementing a Generic Shopping Cart

Now, we’ll create a ShoppingCart class that uses generics. This class can hold any type of fruit while ensuring type safety.

import java.util.ArrayList;

public class ShoppingCart<T extends Fruit> { // Ensures only Fruit or its subclasses are allowed, if you want a more generic cart remove it
    private ArrayList<T> items = new ArrayList<>();

    public void addItem(T item) {
        items.add(item);
    }

    public void removeItem(T item) {
        items.remove(item);
    }

    public void displayItems() {
        for (T item : items) {
            System.out.println(item.getName());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

By specifying T extends Fruit, we restrict the generic type T to the Fruit class or its subclasses. This prevents unrelated objects from being added to the cart.


Step 3: Using the Shopping Cart

Let’s see how we can use the ShoppingCart class with our custom fruit objects.

public class Main {
    public static void main(String[] args) {
        ShoppingCart<Fruit> fruitCart = new ShoppingCart<>();

        // Adding fruits to the cart
        fruitCart.addItem(new Banana());
        fruitCart.addItem(new Apple());
        fruitCart.addItem(new Grape());

        // Displaying the cart's contents
        System.out.println("Shopping Cart Items:");
        fruitCart.displayItems();

        // Removing an item
        fruitCart.removeItem(new Apple()); // Only removes if the object matches (equals method can be overridden for advanced handling)
        System.out.println("After removing Apple:");
        fruitCart.displayItems();
    }
}
Enter fullscreen mode Exit fullscreen mode

Benefits of Using Generics

  1. Type Safety: By restricting T to Fruit or its subclasses, we prevent adding invalid types.
  2. Flexibility: The shopping cart can hold any type of Fruit, making it reusable for various objects.
  3. Elimination of Casting: There’s no need to cast objects when retrieving them from the cart, reducing runtime errors.

Conclusion

Using generics with custom classes, like our Fruit hierarchy, showcases the flexibility and power of this Java feature. The ShoppingCart class demonstrates how we can enforce type safety while maintaining a reusable and flexible structure.

This example is not only practical but also highlights how generics help in writing cleaner and more maintainable code. Whether you're building a real application or learning the fundamentals, generics are an indispensable tool for any Java developer.


📍 Reference

👋 Talk to me

Top comments (1)

Collapse
 
computerwhiz profile image
Wesley Branton • Edited

This is not demonstration of Java generics.

The use of a generic in ShoppingCart is unnecessary and could be done without the generic just by using Fruit.

The reason the code works is because of inheritance, not the use of a generic. The Fruit subclasses and so they can be added to the ArrayList. An identical implementation could be achieved using a Fruit[] array.