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:
<span>String</span><span>[]</span> <span>fruits</span> <span>=</span> <span>new</span> <span>String</span><span>[</span><span>3</span><span>];</span><span>fruits</span><span>[</span><span>0</span><span>]</span> <span>=</span> <span>"Apple"</span><span>;</span> <span>// Valid</span><span>fruits</span><span>[</span><span>1</span><span>]</span> <span>=</span> <span>123</span><span>;</span> <span>// Compile-time error</span><span>String</span><span>[]</span> <span>fruits</span> <span>=</span> <span>new</span> <span>String</span><span>[</span><span>3</span><span>];</span> <span>fruits</span><span>[</span><span>0</span><span>]</span> <span>=</span> <span>"Apple"</span><span>;</span> <span>// Valid</span> <span>fruits</span><span>[</span><span>1</span><span>]</span> <span>=</span> <span>123</span><span>;</span> <span>// Compile-time error</span>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.
<span>// Base class for all fruits</span><span>public</span> <span>abstract</span> <span>class</span> <span>Fruit</span> <span>{</span><span>private</span> <span>String</span> <span>name</span><span>;</span><span>public</span> <span>Fruit</span><span>(</span><span>String</span> <span>name</span><span>)</span> <span>{</span><span>this</span><span>.</span><span>name</span> <span>=</span> <span>name</span><span>;</span><span>}</span><span>public</span> <span>String</span> <span>getName</span><span>()</span> <span>{</span><span>return</span> <span>name</span><span>;</span><span>}</span><span>}</span><span>// Specific fruit classes</span><span>public</span> <span>class</span> <span>Banana</span> <span>extends</span> <span>Fruit</span> <span>{</span><span>public</span> <span>Banana</span><span>()</span> <span>{</span><span>super</span><span>(</span><span>"Banana"</span><span>);</span><span>}</span><span>}</span><span>public</span> <span>class</span> <span>Apple</span> <span>extends</span> <span>Fruit</span> <span>{</span><span>public</span> <span>Apple</span><span>()</span> <span>{</span><span>super</span><span>(</span><span>"Apple"</span><span>);</span><span>}</span><span>}</span><span>public</span> <span>class</span> <span>Grape</span> <span>extends</span> <span>Fruit</span> <span>{</span><span>public</span> <span>Grape</span><span>()</span> <span>{</span><span>super</span><span>(</span><span>"Grape"</span><span>);</span><span>}</span><span>}</span><span>// Base class for all fruits</span> <span>public</span> <span>abstract</span> <span>class</span> <span>Fruit</span> <span>{</span> <span>private</span> <span>String</span> <span>name</span><span>;</span> <span>public</span> <span>Fruit</span><span>(</span><span>String</span> <span>name</span><span>)</span> <span>{</span> <span>this</span><span>.</span><span>name</span> <span>=</span> <span>name</span><span>;</span> <span>}</span> <span>public</span> <span>String</span> <span>getName</span><span>()</span> <span>{</span> <span>return</span> <span>name</span><span>;</span> <span>}</span> <span>}</span> <span>// Specific fruit classes</span> <span>public</span> <span>class</span> <span>Banana</span> <span>extends</span> <span>Fruit</span> <span>{</span> <span>public</span> <span>Banana</span><span>()</span> <span>{</span> <span>super</span><span>(</span><span>"Banana"</span><span>);</span> <span>}</span> <span>}</span> <span>public</span> <span>class</span> <span>Apple</span> <span>extends</span> <span>Fruit</span> <span>{</span> <span>public</span> <span>Apple</span><span>()</span> <span>{</span> <span>super</span><span>(</span><span>"Apple"</span><span>);</span> <span>}</span> <span>}</span> <span>public</span> <span>class</span> <span>Grape</span> <span>extends</span> <span>Fruit</span> <span>{</span> <span>public</span> <span>Grape</span><span>()</span> <span>{</span> <span>super</span><span>(</span><span>"Grape"</span><span>);</span> <span>}</span> <span>}</span>// 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.
<span>import</span> <span>java.util.ArrayList</span><span>;</span><span>public</span> <span>class</span> <span>ShoppingCart</span><span><</span><span>T</span> <span>extends</span> <span>Fruit</span><span>></span> <span>{</span> <span>// Ensures only Fruit or its subclasses are allowed, if you want a more generic cart remove it</span><span>private</span> <span>ArrayList</span><span><</span><span>T</span><span>></span> <span>items</span> <span>=</span> <span>new</span> <span>ArrayList</span><span><>();</span><span>public</span> <span>void</span> <span>addItem</span><span>(</span><span>T</span> <span>item</span><span>)</span> <span>{</span><span>items</span><span>.</span><span>add</span><span>(</span><span>item</span><span>);</span><span>}</span><span>public</span> <span>void</span> <span>removeItem</span><span>(</span><span>T</span> <span>item</span><span>)</span> <span>{</span><span>items</span><span>.</span><span>remove</span><span>(</span><span>item</span><span>);</span><span>}</span><span>public</span> <span>void</span> <span>displayItems</span><span>()</span> <span>{</span><span>for</span> <span>(</span><span>T</span> <span>item</span> <span>:</span> <span>items</span><span>)</span> <span>{</span><span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>item</span><span>.</span><span>getName</span><span>());</span><span>}</span><span>}</span><span>}</span><span>import</span> <span>java.util.ArrayList</span><span>;</span> <span>public</span> <span>class</span> <span>ShoppingCart</span><span><</span><span>T</span> <span>extends</span> <span>Fruit</span><span>></span> <span>{</span> <span>// Ensures only Fruit or its subclasses are allowed, if you want a more generic cart remove it</span> <span>private</span> <span>ArrayList</span><span><</span><span>T</span><span>></span> <span>items</span> <span>=</span> <span>new</span> <span>ArrayList</span><span><>();</span> <span>public</span> <span>void</span> <span>addItem</span><span>(</span><span>T</span> <span>item</span><span>)</span> <span>{</span> <span>items</span><span>.</span><span>add</span><span>(</span><span>item</span><span>);</span> <span>}</span> <span>public</span> <span>void</span> <span>removeItem</span><span>(</span><span>T</span> <span>item</span><span>)</span> <span>{</span> <span>items</span><span>.</span><span>remove</span><span>(</span><span>item</span><span>);</span> <span>}</span> <span>public</span> <span>void</span> <span>displayItems</span><span>()</span> <span>{</span> <span>for</span> <span>(</span><span>T</span> <span>item</span> <span>:</span> <span>items</span><span>)</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>item</span><span>.</span><span>getName</span><span>());</span> <span>}</span> <span>}</span> <span>}</span>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.
<span>public</span> <span>class</span> <span>Main</span> <span>{</span><span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>[]</span> <span>args</span><span>)</span> <span>{</span><span>ShoppingCart</span><span><</span><span>Fruit</span><span>></span> <span>fruitCart</span> <span>=</span> <span>new</span> <span>ShoppingCart</span><span><>();</span><span>// Adding fruits to the cart</span><span>fruitCart</span><span>.</span><span>addItem</span><span>(</span><span>new</span> <span>Banana</span><span>());</span><span>fruitCart</span><span>.</span><span>addItem</span><span>(</span><span>new</span> <span>Apple</span><span>());</span><span>fruitCart</span><span>.</span><span>addItem</span><span>(</span><span>new</span> <span>Grape</span><span>());</span><span>// Displaying the cart's contents</span><span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Shopping Cart Items:"</span><span>);</span><span>fruitCart</span><span>.</span><span>displayItems</span><span>();</span><span>// Removing an item</span><span>fruitCart</span><span>.</span><span>removeItem</span><span>(</span><span>new</span> <span>Apple</span><span>());</span> <span>// Only removes if the object matches (equals method can be overridden for advanced handling)</span><span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"After removing Apple:"</span><span>);</span><span>fruitCart</span><span>.</span><span>displayItems</span><span>();</span><span>}</span><span>}</span><span>public</span> <span>class</span> <span>Main</span> <span>{</span> <span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>[]</span> <span>args</span><span>)</span> <span>{</span> <span>ShoppingCart</span><span><</span><span>Fruit</span><span>></span> <span>fruitCart</span> <span>=</span> <span>new</span> <span>ShoppingCart</span><span><>();</span> <span>// Adding fruits to the cart</span> <span>fruitCart</span><span>.</span><span>addItem</span><span>(</span><span>new</span> <span>Banana</span><span>());</span> <span>fruitCart</span><span>.</span><span>addItem</span><span>(</span><span>new</span> <span>Apple</span><span>());</span> <span>fruitCart</span><span>.</span><span>addItem</span><span>(</span><span>new</span> <span>Grape</span><span>());</span> <span>// Displaying the cart's contents</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Shopping Cart Items:"</span><span>);</span> <span>fruitCart</span><span>.</span><span>displayItems</span><span>();</span> <span>// Removing an item</span> <span>fruitCart</span><span>.</span><span>removeItem</span><span>(</span><span>new</span> <span>Apple</span><span>());</span> <span>// Only removes if the object matches (equals method can be overridden for advanced handling)</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"After removing Apple:"</span><span>);</span> <span>fruitCart</span><span>.</span><span>displayItems</span><span>();</span> <span>}</span> <span>}</span>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
- Type Safety: By restricting
T
toFruit
or its subclasses, we prevent adding invalid types. - Flexibility: The shopping cart can hold any type of
Fruit
, making it reusable for various objects. - 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
原文链接:Understanding Generics in Java: A Shopping Cart Example with Custom Classes
暂无评论内容