Java Generics<Part-2>: Getting your hands dirty (Really?)

In my previous post – Java Generics: <Part-1>, we learnt the basics of Java Generics, and how to use it. Now, it’s time to do some actual work. In this post, we’ll learn how to write and instantiate our own generic types. Further down, we’ll also see how we can write a generic method in a non-generic type, which will be useful if you don’t want your entire class to be generic.

Writing your own Generic Type:

Generic classes are normal classes with one or more type parameters added to it’s definition. The type parameter are declared in angular brackets – <> following the class name. Let’s see how you declare a very simple generic class:

public class Container<T> {
    private List<T> list;
    public Container() {
        this.list = new ArrayList<T>();
    }
    public List<T> getList() { return this.list; }
}

The Container class declares a single type parameter T. As you can see, the type of field list is <List>. We can used the type declared as a part of class declaration in the members declared inside that class. Though there are some restriction, which we’ll see later on. Then we have initialized the list inside the constructor. Again we pass the same type parameter T as actual type argument while instantiating the generic ArrayList<E> type. Though it is not really an actual type argument, but it’s for ArrayList<E>, where E is replaced by T. The type parameters T will be replaced by the actual type argument we use to instantiate our generic class, as shown below:

public static void main(String[] args) {
    Container<String> strContainer = new Container<String>(); 
    List<String> strList = strContainer.getList();
    Container<Integer> intContainer = new Container<Integer>();
    List<Integer> intList = intContainer.getList();
}

We have two concrete instantiation1 of our generic container (also called, concrete parameterized type) – one with String as actual type argument, and one with Integer as actual type argument. Notice, how the getList() method returns the List of corresponding type. You can compile and execute that code, and see that it would work fine. Well, currently it isn’t doing much, so you wouldn’t get any output.

1As we’ll learn later, there is another way to instantiate a generic type called wildcard instantiation(Also called, wildcard parameterized type).

So, the basic idea is, we declare a generic class, and then we instantiate the same class with different type based on our requirement. This makes our task simple. We don’t have to write different container for say – Integer, or String. Apart from that, the code is typesafe, as we know the Container<String> holds a String, so the getList() method will return a List<String> only.

All instantiation of a generic type share the same runtime type.

As we know that there is no difference between generic and non-generic code at runtime. All the generic type information is removed by the compiler during the type erasure process. That means, all the instantiation of a generic type are same at runtime. We say that generics are not reified. Like, List<String>, List<Integer>, List<Date>, etc, all are just a List. They share the same runtime type. To see how, try the following code:

List<String> strList = new ArrayList<String>();
List<Integer> intList = new ArrayList<Integer>(); 
System.out.println(strList.getClass());   // java.util.ArrayList
System.out.println(intList.getClass());   // java.util.ArrayList
System.out.println(strList.getClass() == intList.getClass());   // true

Does this behaviour affect the way you can write code? Certainly yes. The way Java Generics is implemented, restrict you to do certain things which might seem to you obvious on first look. Here’s the most common of them.

Generics are invariant:

This simply means that instantiation of a generic type with two covariant type argument, doesn’t make the parameterized type covariant themselves. So, even though Number is a superclass of Integer, the parameterized type List<Number> and List<Integer> are not. So, you cannot write code like these:

List<Number> list = new ArrayList<Integer>();  // Illegal: Won't compile

List<Integer> intList = new ArrayList<Integer>();
List<Number> numList = intList;                // Illegal too

So, the polymorphism concept doesn’t apply on generics. Now the question arises, why? That’s because JVM cannot differentiate between a List<Number> and a List<Integer> at runtime. They both are List at runtime as already explained. So, if compiler allowed such assignment as above, then it may cause havoc at runtime. Let me show you how.

Consider you have a superclass Animal and it’s two subclasses – Dog and Cat. Now consider the code below:

List<Dog> dogs = new ArrayList<Dog>();
List<Animal> animals = dogs;    // Suppose it was allowed

// Below code is valid, since Cat is a subclass of Animal, we can keep it in a List<Animal>
animals.add(new Cat("mewww"));

// And we do this - get dog at index 0 from dogs
Dog dog = dogs.get(0);    // Oh Dear! You assigned a Cat to a Dog reference.

You see what happened? The last assignment there would throw a ClassCastException at runtime. That is why 2nd assignment is not allowed, and the compiler stops you there only showing you a compiler error.
So, isn’t there any way to achieve polymorphic behaviour in generics? Of course there is. We’ll see how we can achieve this using wildcards.

Generic Methods:

Generic methods are methods that declare type parameters. You declare the type parameters for a generic method in the same way you would do for a generic type. You give them inside an angular brackets. Difference is, it is not after the method name, but just before the return type of the method.

Suppose you want to write a method to convert from an array to a List. Without generics, you would have to write different methods for different types of array. Like one for String[] to List<String>, another for Integer[] to List<Integer>. For example, consider an example for List<Integer>:

public static List<Integer> toList(int... array) {
    List<Integer> list = new ArrayList<Integer>();
    for (int elem: array) {
        list.add(elem);
    }
    return list;
}

This might soon become cumbersome, as it’s not practical enough to have overloaded methods for different types. Only if there was a way to have a single method handle all the types.

Generic method to the rescue:

So there it is. You can write a single generic method, which will handle all the types for you. This is how you write it:

@SafeVarargs   // Don't worry about this annotation. We'll discuss it later, why it's needed
public static <T> List<T> toList(T... array) {
    List<T> list = new ArrayList<T>();	
    for (T elem: array) {
        list.add(elem);
    }		
    return list;
}

The <T> before the return type of the method declares a type parameter T whose scope is confined to the method itself. It is similar to defining the formal parameter for a method. And similar to formal parameters, generic type parameters are part of method signatures. You can also declare multiple type parameters, just as you can declare multiple formal parameters. Just put them in the angular bracket, separated by comma <T, S, U>.

How do you invoke a generic method?

Just like any normal method. The type parameters will be inferred based on the type of arguments you passed, just like they are inferred while instantiating a generic type. So, if you pass a Integer[] and type T will be inferred as Integer, if you pass String[], it will be inferred as String. If you have noticed, I’ve used varargs as the formal parameter type. This will give us the flexibility to invoke the method without passing an explicitly created array. Actually, there might be some issue with using varargs of a type parameter, or a parameterized type, which we’ll discuss later. That is why I’ve used @SafeVarargs annotation there.

So, this is how you would invoke the method:

public static void main(String... args) {
    List<String> strList = toList("a", "b", "c"); // T inferred as String. Return List<String>
    List<Integer> intList = toList(1, 2, 3);      // T inferred as Integer. Return List<Integer>
    List<Box> boxList = toList(new Box(5), new Box(10));  // T inferred as Box. Return List<Box>
}

So you can see the advantage. A single method is sufficient to fulfill our need. With this, I’ll wrap up this post here only.

Advertisements

One Response to Java Generics<Part-2>: Getting your hands dirty (Really?)

  1. Salil says:

    Well explained. Keep up the good work.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: