Avantgarde

On a quest to understand functional programming better

Java Generics by Example

| Comments

In this post, I’m going to talk about some actual examples on how we can use Java generics to make our own code type safe.

In my last article, I explained how generics provide type safety in our code. More specifically, I explained how using code that has been implemented by other people using generics can give us the benefits of type safety.

In this post, I’m going to talk about how we can utilize generics in our own code (not someone else’s code) to achieve type safety and to brag to your friends how esoteric your code is (just kidding, I do not advocate unreadable code, it’s a pain for people reading your code).

And as always, I love examples that explain the motivation. I think it makes much more sense.

Disclaimer: You might have seen these examples somewhere on the Internet. If you find another article that explains these concepts, I would like to give the author credit for that.

Example 1: DRY Principle when Initializing a Generic Class

I just love this pattern. Google’s Guava library provides the Lists factory object to do precisely this.

So you’re just happily declaring lists in Java SE 6 as follows.

1
2
3
List<String> someStrings = new ArrayList<String>();
List<Integer> userIds = new ArrayList<Integer>();
List<User> users = new ArrayList<User>();

Don’t you wish you could just not repeat yourself typing the same type parameter on the right hand side of the expression? I guess Java SE 7 helps alleviate this issue a little bit by incorporating the diamond operator.

1
2
3
List<String> someStrings = new ArrayList<>();
List<Integer> userIds = new ArrayList<>();
List<User> users = new ArrayList<>();

Hmm, slightly better. But we can do even better using Google Guava’s Lists factory object!

1
2
3
4
5
6
7
8
9
/* 
 * Not recommended since this clutters namespace, but this achieves the least
 * characters to initialize lists 
 */
import static com.google.common.collect.Lists.newArrayList;

List<String> someStrings = newArrayList();
List<Integer> userIds = newArrayList();
List<User> users = newArrayList();

“Wait, what? What is this magic? What just happened?”

Let’s take a look at the function signature of newArrayList().

1
2
3
public static <E> ArrayList<E> newArrayList() {
    return new ArrayList<E>();
}

For those of you who are not accustomed to the syntax, you might find this really bizarre. Turns out it’s not as bad as it looks.

Again, the <E> that comes after the static keyword is called a type parameter for the method. It’s like telling the compiler, “Hey, in the scope of the newArrayList() method definition, E is not an actual class name, but a placeholder for some class! So don’t complain when you don’t find a class called E anywhere in the classloader.” Also, by convention, we use single capital letters for type parameters, to avoid confusion with actual classes that exist in the class loader.

So how does the compiler know where to find E? It turns out that when evaluating the expression:

1
List<String> someStrings = newArrayList();

The compiler is smart enough to figure out that since the return value of newArrayList() in this expression has to be assigned to a variable of type List<String>, then the E in ArrayList<E> has to be equal to String. This is called type inference.

And while I’m at it, Scala has a ridiculously much better type inference than Java. It does hurt the Scala compiler’s compile times, though.

Example 2: Map with Predefined Values and Types

You have a map that you wish to use across different parts of your code. The map itself can contain objects of various types. However, you have already defined beforehand what kind of objects are allowed in the map, and also their types.

Let’s say your map can only contain:

  • a user ID of type Integer,
  • a user of type User,
  • a user’s purchase orders that are of type List<Order>.

You will have something like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** A type tag! */
public class Tag<T> {
    private final String name;

    public Tag(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

/** Just some class to hold the predefined Tags */
public class Tags {
    public static final Tag<Integer> USER_ID = new Tag<Integer>("userId");
    public static final Tag<User> USER_INSTANCE = new Tag<User>("userInstance");
    public static final Tag<List<Order>> USER_ORDERS = new Tag<List<Order>>("userOrders");
}

It might seem pointless that we’re passing in a type parameter to Tag when we’re not using it anywhere in the class. The use will hopefully become apparent once I write the next class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TaggedMap {
    private final Map<String, Object> map;

    /* Ideally, we would let the user choose their implementation */
    public TaggedMap() {
        map = new HashMap<String, Object>();
    }

    public <T> void put(Tag<T> tag, T value) {
        map.put(tag.getName(), value);
    }

    public <T> T get(Tag<T> tag) {
        /* 
         * This will throw a compiler warning regarding unchecked casts, but in 
         * fact we do check it since we limit what can go in this map using the 
         * put method above.
         */
        return (T) map.get(tag.getName());
    }
}

I don’t even want to explain the type inference that happens in the example above, since it’s favorably magical. But to briefly explain, the cast in the return statement of get() will always type check by our implementing the TaggedMap class.

We can only put items in the Map using the put() method, which requires you to pass a Tag object containing the type information of the object. Therefore, we know specifically the type of all the items that get put inside the map beforehand. When we get() the items from the map, the class cast will always work.

Examples of usage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
TaggedMap map = new TaggedMap();

map.put(Tags.USER_ID, 123456);

map.put(Tags.USER_INSTANCE, User.withId(map.get(Tags.USER_ID)));

// Let's assume some programmer uses the class, but because he's tired or
// didn't know the code base too well, he thought the type of user IDs was 
// String. 

// This will generate a compiler error, also telling the programmer what 
// he should put in the map if he uses the USER_ID tag
map.put(Tags.USER_ID, "some-user-id");

// Same thing, but the programmer didn't know the return type when he gets the
// user's orders from the map. 

// This will generate a compiler error, also telling the user the type of 
// the object in the map.
Orders orders = map.get(Tags.USER_ORDERS);

The value of using TaggedMap becomes apparent when we realize that we can directly use the Map<String, Object> as follows, leading to a lot of confusion especially in large code bases.

1
2
3
4
5
6
7
8
9
10
11
12
Map<String, Object> map = new HashMap<String, Object>();

// Written by the tech lead.
map.put("userId", 123456);

// The oblivious programmer, again..
String userId = map.get("userId");

// Another oblivious programmer decides to put a UUID for user IDs...
map.put("userId", UUID.randomUUID());

// Chaos ensues...

That’s it for now, I will post more examples as I find more!

Comments