This is a continuation of an introductory Generics tutorial, previous part of which can be found here.
Generics Instantiation Syntax
In the previous post we have learned how to declare a generic class. We have also learned its formal definition. Now it’s time to learn how to create instances of the generic class, formally.
To create an instance of a generic type, we do generic type invocation, which replaces the type parameter with a concrete type. We have seen multiple examples of this in the previous article. Remember our MyPrettySimpleGenericClass from the previous post? If you don’t, here is the definition –
public class MyPrettySimpleGenericClass<E> { private E item; public void setItem(E item) { this.item = item; } public E getItem() { return item; } public void printItem() { System.out.println(item.toString()); }
Then, to create an instance of this class, we do the following –
MyPrettySimpleGenericClass<Integer> item = new MyPrettySimpleGenericClass<Integer>;
In the above code, we have performed a generic type invocation by replacing the type parameter with a concrete type Integer, which makes this object to work with all the integer values. We call Integer a type argument in this case. An invocation of a generic type in this way creates what we call a parameterized type. Our parameterized type in this case is MyPrettySimpleGenericClass<Integer>.
You can pass any non-primitive type as a type argument. You can even pass another parameterized type too, because they are also non-primitive. Consider the following example –
MyPrettySimpleGenericClass<List<Integer>> item = new MyPrettySimpleGenericClass<List<Integer>>();
In the above example, the type parameter is another parameterized type, which was obtained by parameterizing the List<E> generic interface (it’s part of the standard Java API, specifically the Collection API and I intend to write something about them too!).
You can see that this generic method invocation thingy looks somewhat similar to method invocation, except that during a method invocation you pass values as arguments (yes, Java is pass-by-value, and if you are wondering about it, please have patience as I intend to write a post about it in future), and here you pass types as arguments.
Now, some of you might be wondering about the constructor syntax of a generic type, after seeing the <Integer> part right before the parentheses in the above code. Do we also have to put type parameters in the constructor of a generic type?
Short answer – No. You declare a constructor of a generic type like any other constructor. You don’t need to put a type parameter right before the constructor parentheses (in fact you’ll get a compilation error if you try something like that) – putting it right after the class name is enough, unless your constructor itself is a Generic Method (I promise I won’t run away until I write a post about it) which introduces a new type parameter!
In JDK 7 and later, you can omit the type argument during the invocation of the constructor of the generic type, provided that the compiler can determine, or infer (yes, Type Inference will be coming up in a future post), the type argument from the context. Taking advantage of this feature, we can modify our previous object instantiation in the following way –
MyPrettySimpleGenericClass<Integer> item = new MyPrettySimpleGenericClass<>();
This pair of angle brackets, right before the parentheses, is conventionally known as the diamond. It’s of great convenience when writing generics as you have to type less.
Enough theoretical stuff. Let’s put all these things together and write something useful, because the best way to learn something is by actually doing it.
A Useful Example – Implementing a Generic Stack
In this example, we will build a Stack. This stack will be generic – we will use it for different types of items based on different type arguments.
Let’s see the declaration of our Stack class –
import java.util.NoSuchElementException; public class Stack<E> { private long size; private long count; private StackElement top; private class StackElement { private E element; private StackElement next; } public Stack() { this(Long.MAX_VALUE); } public Stack(long size) { this.size = size; this.count = 0; } public long getSize() { return size; } public long getCount() { return count; } public boolean isEmpty() { return count == 0; } public boolean isFull() { return getCount() == size; } public void push(E element) throws UnsupportedOperationException { if (isFull()) { throw new UnsupportedOperationException("Push operation is " + "not supported on full stack"); } StackElement elem = new StackElement(); elem.element = element; elem.next = top; top = elem; count++; } public E pop() throws NoSuchElementException{ if (isEmpty()) { throw new NoSuchElementException("Stack is empty"); } StackElement topElement = top; top = top.next; count--; return topElement.element; } public E peek() throws NoSuchElementException { if (isEmpty()) { throw new NoSuchElementException("Stack is empty"); } return top.element; } }
Some points about the above stack implementation –
- We generally use arrays to store stack elements. This approach won’t work here, because we cannot create an array of generic types. Instead, we will be using the Linked List implementation.
- We have created an inner class to represent a linked list element. This is a perfect example of when an inner class should be used. We wanted to make a type which will store an element of type E, and will also hold a reference to the next element. If we make it a top-level class, we won’t be able to access the type parameter E from it. So, we needed a type which will have access to internal information of another type. As a result, we use an inner class (yes, more on inner classes will come next, in the near future, perhaps within the next ten years…..hmmmmmm……..).
- Notice that we have declared our inner class as private. If we don’t declare it as such, then it may also be accessed from outside Stack, thus leaking our implementation detail in the outside world.
- Notice the declaration of push, pop and peek method. The first one throws an UnsupportedOperationException if an element is pushed while the stack is full, and the last two throws a NoSuchElementException when these operations are done in an empty stack. Some might think of using other exception classes, or building their own with more customization. As for this example, it’s a simple one, so I will be using these two.
Ok, now let’s see a simple test drive –
public class Main { public static void main(String[] args) { Stack<Integer> intStack = new Stack<>(); intStack.push(10); intStack.push(20); intStack.push(30); while(!intStack.isEmpty()) { System.out.println(intStack.pop()); } Stack<Double> doubleStack = new Stack<>(); doubleStack.push(10.5); doubleStack.push(20.34); doubleStack.push(30.56); while(!doubleStack.isEmpty()) { System.out.println(doubleStack.pop()); } Stack<String> stringStack = new Stack<>(); stringStack.push("Hello"); stringStack.push("to"); stringStack.push("Generics!"); while(!stringStack.isEmpty()) { System.out.println(stringStack.pop()); } } }
See how we are using our stack for different types? Aren’t generics awesome 😀 ?
Play with it as long as you want. Tweak the example in as many ways as you like. Try creating an Integer stack and pushing a string into it. Do it the other way round. See what errors/exceptions you get. Also, try to implement other data structures, like Queue, with generics. See how awesome they become!
I have put this implementation in my github repository along with couple of simple test cases (yes, if it was a production quality code, there would have been more and more test cases), in case anyone wants to play with it.
And this is where I’d like to finish the second part. Hope to see you soon!