Filed under: Java, — Tags: Generics, JUnit, TDD, Test driven development — Thomas Sundberg — 2011-05-22
Generics has been a a part of Java since Java 5. It will allow you to write code that handles types without deciding
which type upfront. This is of course very useful for collections. It used to be done using the inheritance system
and casting Object
to the type you knew should be used.
Using Object works, but the objects that are being moved around must be casted to the correct type before they can
be used. Casting is done runtime. It means that there are always a risk for a ClassCastException
. The
risk is reduced when you use Generics since the check is done at compile time.
The risk for ClassCastException
is of course very small if you always test drive your code.
I decided that I should create a Stack so I could explore the Generics in Java a little bit more.
The implementation is as follows.
Let's start with a simple case where I define a Stack that only handles String content. The tests for it looks like this:
package se.sigma.educational; import org.junit.Test; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; public class StackTest { @Test public void pushAndTop() { Stack stack = new Stack(); String item = "My test item"; stack.push(item); assertThat(stack.top(), is(item)); } @Test public void pop() { Stack stack = new Stack(); String item = "My test item"; stack.push(item); assertThat(stack.pop(), is(item)); assertThat(stack.isEmpty(), is(true)); } }
It's really not much, I create a stack and add a string to it. I verify that the string is there. Then I add another string and verifies that it can be removed.
The String stack implementation looks like this:
package se.sigma.educational; import java.util.LinkedList; import java.util.List; public class Stack { private List<String> content; public Stack() { content = new LinkedList<String>(); } public void push(String item) { content.add(item); } public String top() { return content.get(0); } public String pop() { return content.remove(0); } public boolean isEmpty() { return content.isEmpty(); } }
I use a LinkedList to store the content. I define that the list should only contain Strings. I return String from top and pop.
This Stack can only handle items with the type String. It doesn't handle any corner cases. It will be ugly if I pop an empty stack.
The test for a generic implementation may look like this:
package se.sigma.educational; import org.junit.Test; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; public class StackTest { @Test public void pushAndTop() { Stack<String> stack = new Stack<String>(); String item = "My test item"; stack.push(item); assertThat(stack.top(), is(item)); } @Test public void pop() { Stack<String> stack = new Stack<String>(); String item = "My test item"; stack.push(item); assertThat(stack.pop(), is(item)); assertThat(stack.isEmpty(), is(true)); } }
I create the Stack and I define that it should hold String objects. That is the only difference between the test for the string implementation and the test for the generic implementation.
The generic implementation looks like:
package se.sigma.educational; import java.util.LinkedList; import java.util.List; public class Stack<T> { private List<T> content; public Stack() { content = new LinkedList<T>(); } public void push(T item) { content.add(item); } public T top() { return content.get(0); } public T pop() { return content.remove(0); } public boolean isEmpty() { return content.isEmpty(); } }
The largest difference between the string implementation and the generic implementation is that the generic implementation defines a type T that will be used everywhere instead of String as in the previous example.
T is only a symbol and can be changed to anything else. If you like, you could call it type
instead.
Using Generics isn't really difficult. Replace the type you use with any symbol, I use T in this example. Make sure that the calling code defines the type that should be used.
That's it folks!