Table of contents
1.
Introduction
2.
What are Java Generics?
3.
Factory Pattern
3.1.
Code
3.2.
Java
4.
Strategy Pattern
4.1.
Code
4.2.
Java
5.
Bridge Pattern
5.1.
Code
5.2.
Java
6.
Adapter Pattern
6.1.
Code
6.2.
Java
7.
Visitor Pattern
7.1.
Code
7.2.
Java
8.
Builder Pattern
8.1.
Code
8.2.
Java
9.
Frequently Asked Questions
9.1.
What is the purpose of wildcards in Generics?
9.2.
What are Java Generics Design Patterns?
9.3.
What is a Generic Interface in Java?
10.
Conclusion
Last Updated: Mar 27, 2024
Medium

Java Generics Design Patterns

Author Shiva
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

Java is a versatile programming language widely used for its object-oriented capabilities. Among its many features, Java Generics is a powerful tool for creating flexible and reusable code. Also, the code should be readable and easier to understand for developers. This is why design patterns are so important. They offer a clear and structured approach to solving common programming problems.

Java Generics Design Patterns

In this article, we'll explore Java Generics Design Patterns, and common strategies for using generics effectively. We'll keep things simple and provide straightforward examples to help you grasp these concepts.

Read more, how to run java program

What are Java Generics?

Java generics allow the creation of classes, interfaces and methods that can handle types of parameters. This means you can write code that can work with data types while ensuring type safety. In other terms generics help, in writing code that's more flexible and reusable by parameterizing the involved types.

Before we explore design patterns lets understand why generics are advantageous. Imagine you are developing a collection to store kinds of objects such as integers, strings and custom objects. Without generics you would have to use the raw Object type and cast values when retrieving them. This approach can lead to runtime errors. Make your code less clear. Generics provide a solution, to these issues.

Factory Pattern

The factory pattern is a creational design pattern. Creational design patterns provide various object creation mechanisms, which increase flexibility and reuse of existing code. Factory pattern provides an interface for creating objects. Generics enhance this pattern by allowing you to create a factory that can produce objects of different types.

Code

  • Java

Java

class Factory<T> {
   private Class<T> clazz;


   public Factory(Class<T> clazz) {
       this.clazz = clazz;
   }


   public T create() {
       try {
           return clazz.newInstance();
       } catch (InstantiationException | IllegalAccessException e) {
           e.printStackTrace();
           return null;
       }
   }
}


class Circle {
   void draw() {
       System.out.println("Drawing a circle");
   }
}


class Rectangle {
   void draw() {
       System.out.println("Drawing a rectangle");
   }
}


public class Main {
   public static void main(String[] args) {
       Factory<Circle> circleFactory = new Factory<>(Circle.class);
       Circle circle = circleFactory.create();
       circle.draw();


       Factory<Rectangle> rectangleFactory = new Factory<>(Rectangle.class);
       Rectangle rectangle = rectangleFactory.create();
       rectangle.draw();
   }
}
You can also try this code with Online Java Compiler
Run Code

Output:

output

Explanation:

Here we defined a generic Factory class. Which creates instances of different classes based on the provided class type. The Factory is parameterized with a class type (T), allowing it to produce objects of any class. It uses Java's reflection mechanism to create instances of the specified class type. In the main method, it illustrates the creation of Circle and Rectangle objects using this factory.

Also see, Java Ioexception

Strategy Pattern

The Strategy Pattern is a behavioural design pattern. Behavioural design patterns are concerned with algorithms and the assignment of responsibilities between objects. Strategy Pattern allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. Generics help create a strategy that works with various types.

Code

  • Java

Java

import java.util.*;

// Strategy interface for sorting
interface SortingStrategy<T> {
   void sort(List<T> list);
}

// Concrete sorting strategies
class BubbleSort<T extends Comparable<T>> implements SortingStrategy<T> {
   @Override
   public void sort(List<T> list) {
       for (int i = 0; i < list.size() - 1; i++) {
           for (int j = 0; j < list.size() - 1 - i; j++) {
               if (list.get(j).compareTo(list.get(j + 1)) > 0) {
                   Collections.swap(list, j, j + 1);
               }
           }
       }
   }
}

class QuickSort<T extends Comparable<T>> implements SortingStrategy<T> {
   @Override
   public void sort(List<T> list) {
       if (list.size() <= 1) {
           return;
       }

       T pivot = list.get(list.size() / 2);
       List<T> less = new ArrayList<>();
       List<T> equal = new ArrayList<>();
       List<T> greater = new ArrayList<>();


       for (T element : list) {
           int cmp = element.compareTo(pivot);
           if (cmp < 0) {
               less.add(element);
           } else if (cmp > 0) {
               greater.add(element);
           } else {
               equal.add(element);
           }
       }


       sort(less);
       sort(greater);
       list.clear();
       list.addAll(less);
       list.addAll(equal);
       list.addAll(greater);
   }
}


// Generic Sorter class that uses a sorting strategy
class Sorter<T> {
   private SortingStrategy<T> strategy;

   public Sorter(SortingStrategy<T> strategy) {
       this.strategy = strategy;
   }

   public void sortList(List<T> list) {
       strategy.sort(list);
   }
}


public class Main {
   public static void main(String[] args) {
       List<Integer> integerList = new ArrayList<>(Arrays.asList(3, 1, 2, 5, 4));

       SortingStrategy<Integer> bubbleSort = new BubbleSort<>();
       SortingStrategy<Integer> quickSort = new QuickSort<>();

       Sorter<Integer> bubbleSorter = new Sorter<>(bubbleSort);
       bubbleSorter.sortList(integerList);

       System.out.println("Sorted Integer List (Bubble Sort): " + integerList);

       List<Integer> anotherIntegerList = new ArrayList<>(Arrays.asList(3, 1, 2, 5, 4));
       Sorter<Integer> quickSorter = new Sorter<>(quickSort);
       quickSorter.sortList(anotherIntegerList);

       System.out.println("Sorted Integer List (Quick Sort): " + anotherIntegerList);
   }
}
You can also try this code with Online Java Compiler
Run Code

Output:

output

Explanation:

In this piece of code we have implemented two sorting techniques; BubbleSort and QuickSort. Both of these techniques follow the SortingStrategy interface. The Sorter class is responsible, for sorting a list of integers based on the chosen strategy provided as a constructor parameter. In the method we create two lists of integers. Sort them using different strategies (BubbleSort and QuickSort). Finally we display the results, on the console.

Bridge Pattern

The Bridge Pattern is a structural design pattern. Structural design patterns explain how to assemble objects and classes into larger structures while keeping these structures flexible and efficient. A Bridge Pattern separates an object's abstraction from its implementation. Generics can simplify this pattern by creating a bridge that works with various types.

Code

  • Java

Java

// Abstraction hierarchy with generics
interface Shape<T> {
   void draw();
   T getType();
}

class Circle implements Shape<String> {
   @Override
   public void draw() {
       System.out.println("Drawing a circle");
   }


   @Override
   public String getType() {
       return "Circle";
   }
}

class Rectangle implements Shape<String> {
   @Override
   public void draw() {
       System.out.println("Drawing a rectangle");
   }
   @Override
   public String getType() {
       return "Rectangle";
   }
}


// Implementation hierarchy with generics
interface Renderer<T> {
   void render(Shape<T> shape);
}

class ShapeRenderer<T> implements Renderer<T> {
   @Override
   public void render(Shape<T> shape) {
       System.out.println("Rendering a " + shape.getType());
       shape.draw();
   }
}

public class Main {
   public static void main(String[] args) {
       Shape<String> circle = new Circle();
       Shape<String> rectangle = new Rectangle();


       Renderer<String> circleRenderer = new ShapeRenderer<>();
       Renderer<String> rectangleRenderer = new ShapeRenderer<>();


       circleRenderer.render(circle);
       rectangleRenderer.render(rectangle);
   }
}
You can also try this code with Online Java Compiler
Run Code

Output: 

output

Explanation:

We have a Shape interface that uses a type parameter, T to represent the data associated with the shape. The Renderer interface is also generic. Has a type parameter, T which represents the output type of the rendering logic. The ShapeRenderer class implements the Renderer interface, with generic type T. It has the ability to render shapes and return an output type if necessary.

Adapter Pattern

Moving on to the Adapter Pattern it falls under the category of design patterns. Structural design patterns focus on how objects and classes can be combined to form structures while maintaining flexibility and efficiency. The Adapter Pattern specifically allows for incompatible interfaces to work together. By utilizing generics it becomes possible to create adapters that can work with types.

Code

  • Java

Java

import java.util.*;
class ListPrinter<T> {
   public void print(List<T> list) {
       for (T item : list) {
           System.out.println(item);
       }
   }
}


public class AdapterPatternExample {
   public static void main(String[] args) {
       List<Integer> integers = Arrays.asList(1, 2, 3);
       ListPrinter<Integer> intListPrinter = new ListPrinter<>();
       intListPrinter.print(integers);


       List<String> strings = Arrays.asList("apple", "banana", "cherry");
       ListPrinter<String> stringListPrinter = new ListPrinter<>();
       stringListPrinter.print(strings);
   }
}
You can also try this code with Online Java Compiler
Run Code

Output: 

output

Explanation:

This Java code showcases the utilization of the Adapter Pattern through a class called `ListPrinter`. Its purpose is to enable the printing of items, from types of lists such as lists containing integers or strings by utilizing a print` method. This design pattern simplifies the process of displaying the contents of lists without requiring print methods, for each type. In this example it demonstrates the usage of the `ListPrinter` class to print both a list of numbers and a list of words.

Visitor Pattern

The Visitor Pattern is a behavioural design pattern. Behavioural design patterns are concerned with algorithms and the assignment of responsibilities between objects. Visitor Pattern allows you to add new operations to objects without changing their class structure. Generics can create generic visitors that work with various types.

Code

  • Java

Java

interface ShapeVisitor<T> {
   T visit(Circle circle);
   T visit(Rectangle rectangle);
}

class AreaCalculator implements ShapeVisitor<Double> {
   @Override
   public Double visit(Circle circle) {
       return Math.PI * circle.getRadius() * circle.getRadius();
   }

   @Override
   public Double visit(Rectangle rectangle) {
       return rectangle.getWidth() * rectangle.getHeight();
   }
}

class Circle {
   private double radius;

   public Circle(double radius) {
       this.radius = radius;
   }

   public double getRadius() {
       return radius;
   }

   public <T> T accept(ShapeVisitor<T> visitor) {
       return visitor.visit(this);
   }
}


class Rectangle {
   private double width;
   private double height;

   public Rectangle(double width, double height) {
       this.width = width;
       this.height = height;
   }

   public double getWidth() {
       return width;
   }

   public double getHeight() {
       return height;
   }

   public <T> T accept(ShapeVisitor<T> visitor) {
       return visitor.visit(this);
   }
}


public class Main {
   public static void main(String[] args) {
       Circle circle = new Circle(5);
       Rectangle rectangle = new Rectangle(4, 6);

       AreaCalculator areaCalculator = new AreaCalculator();
       double circleArea = circle.accept(areaCalculator);
       System.out.println("Circle area: " + circleArea);

       double rectangleArea = rectangle.accept(areaCalculator);
       System.out.println("Rectangle area: " + rectangleArea);
   }
}
You can also try this code with Online Java Compiler
Run Code

Output: 

output

Explanation:

This code showcases the Visitor Design Pattern. It introduces two shapes, Circle and Rectangle both of which have an "accept" method designed for a ShapeVisitor. The AreaCalculator serves as the ShapeVisitor. Which is responsible, for computing the area of these shapes. In the function we create instances of Circle and Rectangle calculate their areas using the AreaCalculator and then print out the results.

Also see,  Eclipse ide for Java Developers

Builder Pattern

The Builder Pattern is a creational design pattern. Creational design patterns provide various object creation mechanisms, which increase flexibility and reuse of existing code. Builder Pattern separates the construction of a complex object from its representation. Generics can create builders that construct objects of different types.

Code

  • Java

Java

class Report {
   private String content;

   public Report(String content) {
       this.content = content;
   }

   public void print() {
       System.out.println("Report: " + content);
   }
}

class ReportBuilder<T> {
   private T report;

   public ReportBuilder<T> create(String content) {
       report = (T) new Report(content);
       return this;
   }

   public T build() {
       return report;
   }
}

public class Main {
   public static void main(String[] args) {
       ReportBuilder<Report> reportBuilder = new ReportBuilder<>();
       Report report = reportBuilder.create("Sales Report").build();
       report.print();
   }
}
You can also try this code with Online Java Compiler
Run Code

Output:

output

Explanation:

The `Report` class represents a report containing content that can be printed out. The `ReportBuilder` class assists, in the creation of reports. Within the `Main` class an example is given on how to utilize the builder to generate a "Sales Report," followed by printing out the report. It's similar, to creating a report and then displaying it on the screen.

Frequently Asked Questions

What is the purpose of wildcards in Generics?

Wildcards in Generics offer the advantage of working with types and providing flexibility during the creation of methods or classes. They are represented by "<?>". Allow for accepting and manipulating data of types without specifying the exact type.

What are Java Generics Design Patterns?

Java Generics Design Patterns refer to strategies used to utilize Generics in your code. These patterns include Factory Pattern, Strategy Pattern, Bridge Pattern, Adapter Pattern, Visitor Pattern and Builder Pattern. They present structured approaches, for handling code scenarios.

What is a Generic Interface in Java?

Well it's an interface that allows you to declare one or more type parameters. By doing classes can implement this interface with types. This important feature enables you to define an interface for types while still providing specific implementations, for each type.

Conclusion

In this article we explored the world of Java Generics Design Patterns. When these patterns are combined with practical examples they become much easier to grasp. Some of the patterns we covered include the Factory Pattern, Strategy Pattern, Bridge Pattern, Adapter Pattern, Visitor Pattern and Builder Pattern.

Recommended Readings:

You may refer to our Guided Path on Code Ninjas Studios for enhancing your skill set on DSACompetitive ProgrammingSystem Design, etc. Check out essential interview questions, practice our available mock tests, look at the interview bundle for interview preparations, and so much more!

Happy Learning, Ninja!

Live masterclass