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:

<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

  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

原文链接:Understanding Generics in Java: A Shopping Cart Example with Custom Classes

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
Stop cheating on your future with your past... it's over.
别再用你的过去欺骗你的未来,过去已经过去了
评论 抢沙发

请登录后发表评论

    暂无评论内容