Avantgarde

On a quest to understand functional programming better

Gentle Introduction to Generics

| Comments

There are a lot of articles explaining the theory behind Java generics, and whenever I read about these articles, I always feel confused, because I think it’s always confusing to learn about an abstract concept without concrete examples and actual use cases. Try reading Oracle’s Java tutorial on generics. I think I know generics fairly enough to be effective with generics and I still find the article confusing.

The concept of Java generics itself is a hard but interesting one to understand, and when articles start by telling people what it is and what the benefits are in some esoteric terms, it usually just doesn’t help me.

In this article, I’m going to take a shot at explaining generics by outlining why it was incorporated as a language feature in the first place Having said all of the above, you do need to have a vague sense of what generics are before jumping to my explanation below (i.e. you should have at least seen them and used them before).

Introduction

So why are generics implemented in the first place anyway?

Most of us will get acquainted very early with Java generics through the use of Java’s collection library. Among the Java standard collections library, one of the most often used data structures in computer science is a list, which is an sequence of values. The following code snippet shows how you would declare some list of items in Java SE 6.

1
2
3
4
5
6
List<String> names = new List<String>();
names.add("Jack");
names.add("Jill");

for (String name : names)
    System.out.println("My friend's name is " + name);

The class that you put in the angled brackets in the example above when declaring the variable names of class List defines what the list contains. In the example above, it’s String. Formally speaking, String is called the type parameter of List in this case.

Life Without Generics

Now, imagine how life in the Java world would be without generics. Imagine that angled brackets are the compiler’s worst enemy, and it will gladly let you know by throwing a barrage of syntax errors every time you put a left angle bracket after a type.

In the imaginary Java land, the example above transforms to the following:

1
2
3
4
5
6
7
8
List names = new List();
names.add("Jack");
names.add("Jill");

for (Object thing : names) {
    String name = (Object) thing;
    System.out.println("My friend's name is " + name);
}

Why do we have to use a temporary variable of type Object to iterate through the things in the list? If you were designing the implementation of the List class, you would need to declare some instance variable inside the class to hold the items that will be put in the list.

To simplify things, let’s just assume that the instance variable in concern is an array of items. What would the type of the item be? It can’t be an Integer, or String, or some specific data type, since then only people who have the need of using an integer or string list can use your class.

Anyway, my point is that the type of the item has to be Object. Since Object is the superclass of all Java classes, it only makes sense to use Object.

Further Motivation

Now, you might say, “Well, that doesn’t seem too bad. I mean, we only need to cast the Object to a String, right? Why deal with so much hassle to implement these angle brackets?”

Imagine you’re still in the imaginary Java land, where generics are frowned upon. Being the good Java programmer you are, you implemented a class that looks like this, has good encapsulation, and all the good OOP stuff.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    package com.thefabulousme;

    import java.util.*;

    /* Because who doesn't like to talk about themselves? */
    public class Me {
        private List favoriteThings;

        public Me() {
            this.favoriteThings = new List();

            /* I just love prime numbers and the 6th element of the Fibonacci sequence. */
            favoriteThings.add(new Integer(13));
            /* Can't forget the answer to the ultimate question of life */
            favoriteThings.add(new Integer(42));
        }

        public List getFavoriteThings() {
            return this.favoriteThings;
        }

        /* Because I don't want people to change what I like. */
        public void setFavoriteThings(List favoriteThings) {
            String message = "Hey, don't touch my favorite things!";
            throw new IllegalArgumentException(message);
        }
    }

For some (unclear) reason, you become famous, and you hire a programmer to build you a website because you don’t have time anymore. And you told him to use your Me class since it contains your personal info already nicely coded in Java. However, it’s located in some private folder so you can’t give the source code to your programmer, and all you can tell him is the method signature of the class. You gave him a piece of paper with the following written since you had to leave in a hurry.

1
2
3
4
5
public class Me {
    public Me() { ... }
    public List getFavoriteThings() { ... }
    public void setFavoriteThings(List favoriteThings) { ... }
}

Your fellow programmer then takes the note and the compiled bytecode, and goes ahead to code the website. Here’s what he came up with. Oh, and also, while he codes the website, he expresses his deepest inner thoughts with comments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.some.obnoxious.client.autobiography;

// Is he even serious? The "fabulous me"...?
import com.thefabulousme.Me;

public class SomeWebsite {
    // ...

    // Sheez, does he really have to name the class "Me"? It's so stupid.
    public String getAutobiography(Me myObnoxiousClient) {
        String autobiography = new String("Stuff He Likes: \n");

        // Hmm, let's see, what do I do with this Me object. Oh, there are
        // only two methods in this class. I think I'm not going to touch
        // Me#setFavoriteThings() because I don't need to change what his
        // favorite things are. Oh well, let's get this stuff done. 
        List stuffThatMyClientApparentlyLikes
            = myObnoxiousClient.getFavoriteThings();

        for (Object stuff : stuffThatMyClientApparentlyLikes) {
            // His favorite things should be of type String, right? I 
            // mean, I need his class to be able to write the website, 
            // so, yeah, of course. 
            String thing = (String) stuff;

            // Just keep adding stuff to autobiography.
            autobiography += new String("\t - " + thing + "\n");
        }
        // Can't forget the new line. 
        autobiography += new String("\n");

        // Phew, done for the day. Time to submit code and go home. 
        return autobiography;
    }

    // ...
}

Well, just in case you were wondering what happened to the aspiring Java programmer, you fired him because his code did not work. It blew up with a ClassCastException when you were showcasing the website during a press release.

Upon doing a code review, you found out about all his darkest thoughts and the fact he did not use a StringBuilder. And the only thing you got out of this is the fact that afterwards his comments made you contemplate your silliness.

In all seriousness, please don’t code like him, it wastes valuable resources trying to create a new String when the only thing you really need to do is string concatenation. Also, use a StringBuilder instead which is good enough for single threaded operations to strings (it’s not thread-safe, but we’ll leave that to another discussion).

Also, don’t comment on every single line. Please.

Lessons

As contrived as the example above could be, not being able to share source code is pretty common if you only have access to the JAR without the source. Also only sharing method names is akin to sharing an interface of the class. It’s sad, but class casting also happens in reality. While I realize class casting sometimes cannot be avoided, it should be done in a safer manner using instanceof only if you really need to (also, I won’t get into this, but pattern matching provides a superior alternative to class casting).

The programmer’s sad ending could have been easily avoided if it were a couple years later and he was using Java SE 1.6, where the generics feature was added.

Really, the takeaway from the story above is that generics provide compile-time type safety. That’s a mouthful, but let me try to explain.

In the (albeit exaggerated) example above, the programmer could only have known that he had converted the object into the wrong type during run time. That is, after the code had been compiled and was running in some server, not until you click on the Autobiography link would the code have exploded.

Now, assuming the getFavoriteThings() method had been declared properly using generics as follows:

1
2
3
public List<Integer> getFavoriteThings() {
    ...
}

It would have been immediately obvious to the programmer that the type of the list was Integer, and even if he still extracted the contents of the list using a temporary variable of type Object, assuming he knew how to program and has some decent understanding that he can’t cast an Integer to a String, he wouldn’t have done that.

The thing is to enable the compiler to warn you during compile-time if your types do not match (type unsafe operations) when you are coding by throwing a warning (or even better, an error), instead of seeing it happen during run-time. Hence the reason generics provide compile-time type safety.

Man, that was kinda fun! Hope this has helped you somewhat!

UPDATE: This article has been updated 1 time since it was first written.

Comments