Do you think IIT Guwahati certified course can help you in your career?
No
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.
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.
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; }
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();
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.
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.
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"; } }
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);
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(); }
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.
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
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.