Bits of Java – Episode 23: Generics in Java

Today we will discuss a bit about the use of generics in Java, which have been part of Java since its version 5.

But what exactly are generics? So, when you build a List object, for instance, you would do something like this:

List<String> names = new ArrayList<>();

where, in between <> you specify the type of the elements you want to put inside your list.

You could also do something like this:

List names = new ArrayList();

but, in this case, you would get a warning from the compiler, since you are not specifying the type of the elements. It would still run, as the compiler will take java.lang.Object as type, which the widest we have in Java!

This is because, if we look at the definition of the List interface we would find something like:

public interface List<E> {
    //missing code...
}

where E is our generic element type. This is what allows you to create a List of whatever object you want!

Why is that useful? Well, in the end, a list is a list, right? You do not want to have a separate List interface for every possible element type you put in there, right? Generics allow you to avoid exactly that! They allow you to define a unique structure that can be then applied to different kinds of types!

You can also create your own generic class, for instance.

public class Box<T> {

}

Here, I am defining a class Box of a generic type T. I can have a box of whatever I want, right? Let’s say I have defined somewhere two other classes Book and Chocolate, for instance. Then I can create a Box for each one of these two types:

Box<Book> booksBox = new Box<Book>();
Box<Chocolate> chocoBox = new Box<Chocolate>();

Of course, in the generic class, you can define fields and methods, using generics as well.

public class Box<T> {
    private List<T> contents = new ArrayList<T>();
    public void add(T element) {
        this.contents.add(element);
    }
    public T getElement(int index) {
        return this.contents.get(index);
    }
}

In here, I have defined a private instance field, which is nothing else than a List, containing the same type of objects as the type of Box.

Then I have defined two methods, which are just wrapper methods of the add and get of a List, so nothing too fancy here, but just to give you an idea.

You can also use generics in a non-generic class! In particular, they can be quite useful when you work with static helper methods.

public class HelperClass {
    public static <T> void print(T t) {
        System.out.println("Printing " + t);
    }
}

In this case, HelperClass is not a generic class, but its method print uses generics. As you can see, contrary to what we did for the methods of Box, here we have to put a <T> before the return type of the method. This is mandatory here because the class itself does not use generics.

We could have also done it in the Box class. However, if we did, in this case, the generic type of the class and the generic type of the methods are not automatically considered the same!! This is why the following would produce some compiler errors:

public class Box<T> {
    private List<T> contents = new ArrayList<T>();
    public <T> void add(T element) {
        this.contents.add(element); //DOES NOT COMPILE!!
    }
    public <T> T getElement(int index) {
        return this.contents.get(index); //DOES NOT COMPILE!!
    }
}

Let’s examine what’s going on here! So, our Box class is a generic class of type T. The contentsfields is a List of the same generic type T. The two methods, however, in this case, define their own generic type, through <T>, which is not necessarily the same as the one declared by the class. So, when we try to add an element of that type to the contents list, or we try to retrieve one, the compiler gives as an error! We need to add some cast operators to make it compile, and also change a bit the naming so the compiler does not get confused!

public class Box<T> {
    private List<T> contents = new ArrayList<T>();
    public <U> void add(U element) {
        this.contents.add((T) element); //OK!!
    }
    public <U> U getElement(int index) {
        return (U) this.contents.get(index); //OK!!
    }
}

Now it is clear which generic refers to what! T is the generic type of the class and of the contents field, while U is the generic type of the methods. To add an element of type U to contents we need first to cast it to T, while to retrieve an element of type U from the list, we have to cast it to U.

As last remark for today, remember when we talked about overloading methods? We said that these are methods with the same name but with a different parameter list. So, you may argue that something like this should be valid:

public void printElements(List<String> names) {
    for(String name : names) {
        System.out.println(name);
    }
}

public void printElements(List<Integer> numbers) {
    for(Integer number : numbers) {
        System.out.println(number);
    }
}

But actually is not! What happens here is that we are using generics, and the generic type is the only thing that differs in the parameter list of the two methods. However, generics are there only till compile time! When this code is compiled, it will look like:

public void printElements(List<Object> names) {
    for(Object name : names) {
        System.out.println(name);
    }
}

public void printElements(List<Object> numbers) {
    for(Object number : numbers) {
        System.out.println(number);
    }
}

So, under the hood, the compiler removes all your generics reference and substitutes them with Object, placing the right casting where necessary. And so, the two methods do not look anymore like valid overloading methods, and you get a compiler error!!

That’s it for today! In the next post we are going even deeper into this topic and we will discuss bounded and unbounded generics types! Stay tuned!!

by Ilenia Salvadori