Generic Programming is one of the most powerful tools for any developer.
Credits: Photo by Eric Prouzet on Unsplash
In this article, we'll try to understand generic programming. We'll start with the underlying challenges which led to the rise of generic programming in the first place. Alongside, we'll see some examples of how it can be implemented using Java Generics.
The Motor Van problem
So, let's say we're in the early days of computers and recently while developing a ticketing system we discovered how to implement a Queue for storing names of people waiting for the tickets. The interface looks as follows,
public class QueueOfStrings QueueOfStrings() # create an empty queue void push(String item) # insert a new string into queue String pop() # remove and return the string least recently added boolean isEmpty() # is the queue empty? int size() # number of strings on the queue
Initially, we only required a queue to put people names in a list to issue them tickets in-order, hence we defined QueueOfStrings(). However, suddenly the demand for more Queues increased and now business needs Queues for phone numbers, motor vans (for parking slots), etc. So, how can provide all the queues? Should we just copy the implementation and change the names and types?
There can be many ways to accomplish that, lets' try to discuss a few, and evaluate them one by one below.
A queue a day keeps problems away?
Attempt 1: Implement a separate queue class for each type. For example, if we need a queue of MotorVan objects, we can define our interface as below,
public class QueueOfMotorVans QueueOfMotorVans() # create an empty queue void push(String item) # insert a new string into queue String pop() # remove and return the string least recently added boolean isEmpty() # is the queue empty? int size() # number of strings on the queue
And, we can copy the underlying implementation replacing Strings with MotorVans. However, below are two underlying issues with this method,
- Rewriting code is tedious and error-prone.
- Maintaining cut-and-pasted code is tedious and error-prone.
Fun Fact - This was the most reasonable approach until Java 1.5.
Let's define one Queue to rule them all!
Attempt 2: Implement a queue with items of type Object. This can help to store all types of objects whether it's Oranges or MotorVans.
QueueOfObjects s = new QueueOfObjects(); MotorVan mv = new MotorVan(); Orange or = new Orange(); s.push(mv); s.push(or); a = (MotorVan) (s.pop()); # Errors at run-time
As mentioned in a comment, popping objects might be error-prone. Another issue is that we'll not be able to identify issues in my application at compile-time (type-casting happens at run time). Hence, not ensuring the correctness of solution.
Now comes the Spiderman - Generics
Attempt 3: Java generics. We can use the following syntax to define the generic definition of the queue -
Class Name<T>. And, now we can create a queue for theoretically anything with just a single line of code as mentioned below.
Queue<MotorVan> s = new Queue<MotorVan>(); MotorVan mv = new MotorVan(); Orange or = new Orange(); s.push(mv); s.push(or); a = s.pop();
This implementation approach helps us by:
- Avoid casting in the client.
- Discover type mismatch errors at compile-time instead of run-time.
Generic Programming enables the solution to more complex problems, by simplifying the process of dealing with a different type of real-world objects. We can see the day to day examples of generics being used in the C++ STL and Java collections library. More powerful real-world examples can be seen in video games, animations, etc.
Caveats: In Java, arrays are covariant and generics are invariant. Hence, sometimes we might need to use a casting trick to enable solutions.